const notyf = new Notyf({
  position: { x: "center", y: "top" },
  types: [
    {
      type: "success",
      background: "#99c959",
      duration: 2000,
    },
    {
      type: "warning",
      background: "#f8b26a",
      duration: 3000,
    },
    {
      type: "error",
      background: "#e15b64",
      duration: 3000,
    },
  ],
});
const registerSW = () => {
  if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("sw.js" + location.search).then(
      (reg) => console.log("Service worker register succeeded"),
      (error) => console.error(`Service worker register failed: ${error}`)
    );
  }
};
window.addEventListener("load", () => registerSW());
const isMobile = navigator.userAgent.match(
  /iPhone|iPad|iPod|Android|BlackBerry|webOS/
);
if (isMobile) {
  const script = document.createElement("script");
  script.src =
    "https://fastly.jsdelivr.net/gh/timruffles/mobile-drag-drop@3.0.0-rc.0/release/index.min.js";
  script.crossOrigin = "anonymous";
  script.defer = true;
  script.onload = () => {
    MobileDragDrop.polyfill();
  };
  document.body.appendChild(script);
  const link = document.createElement("link");
  link.crossOrigin = "anonymous";
  link.rel = "stylesheet";
  link.href =
    "https://fastly.jsdelivr.net/gh/timruffles/mobile-drag-drop@3.0.0-rc.0/release/default.css";
  document.body.appendChild(link);
}
let envAPIEndpoint,
  envAPIKey,
  envAPIModel,
  envGeminiAPIEndpoint,
  envGeminiAPIKey,
  envGeminiAPIModel,
  envClaudeAPIEndpoint,
  envClaudeAPIKey,
  envClaudeAPIModel;
// from fflate@0.8.2
{
  const t = Uint8Array,
    e = Uint16Array,
    n = Int32Array,
    r = new t([
      0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5,
      5, 5, 5, 0, 0, 0, 0,
    ]),
    o = new t([
      0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10,
      11, 11, 12, 12, 13, 13, 0, 0,
    ]),
    l = new t([
      16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
    ]),
    s = (t, r) => {
      const o = new e(31);
      for (let e = 0; e < 31; ++e) o[e] = r += 1 << t[e - 1];
      const l = new n(o[30]);
      for (let t = 1; t < 30; ++t)
        for (let e = o[t]; e < o[t + 1]; ++e) l[e] = ((e - o[t]) << 5) | t;
      return { b: o, r: l };
    },
    { b: f, r: c } = s(r, 2);
  (f[28] = 258), (c[258] = 28);
  const { b: i, r: a } = s(o, 0),
    h = new e(32768);
  for (let t = 0; t < 32768; ++t) {
    let e = ((43690 & t) >> 1) | ((21845 & t) << 1);
    (e = ((52428 & e) >> 2) | ((13107 & e) << 2)),
      (e = ((61680 & e) >> 4) | ((3855 & e) << 4)),
      (h[t] = (((65280 & e) >> 8) | ((255 & e) << 8)) >> 1);
  }
  const u = (t, n, r) => {
      const o = t.length;
      let l = 0;
      const s = new e(n);
      for (; l < o; ++l) t[l] && ++s[t[l] - 1];
      const f = new e(n);
      for (l = 1; l < n; ++l) f[l] = (f[l - 1] + s[l - 1]) << 1;
      let c;
      if (r) {
        c = new e(1 << n);
        const r = 15 - n;
        for (l = 0; l < o; ++l)
          if (t[l]) {
            const e = (l << 4) | t[l],
              o = n - t[l];
            let s = f[t[l] - 1]++ << o;
            for (const t = s | ((1 << o) - 1); s <= t; ++s) c[h[s] >> r] = e;
          }
      } else
        for (c = new e(o), l = 0; l < o; ++l)
          t[l] && (c[l] = h[f[t[l] - 1]++] >> (15 - t[l]));
      return c;
    },
    w = new t(288);
  for (let t = 0; t < 144; ++t) w[t] = 8;
  for (let t = 144; t < 256; ++t) w[t] = 9;
  for (let t = 256; t < 280; ++t) w[t] = 7;
  for (let t = 280; t < 288; ++t) w[t] = 8;
  const g = new t(32);
  for (let t = 0; t < 32; ++t) g[t] = 5;
  const b = u(w, 9, 0),
    d = u(w, 9, 1),
    m = u(g, 5, 0),
    y = u(g, 5, 1),
    M = (t) => {
      let e = t[0];
      for (let n = 1; n < t.length; ++n) t[n] > e && (e = t[n]);
      return e;
    },
    p = (t, e, n) => {
      const r = (e / 8) | 0;
      return ((t[r] | (t[r + 1] << 8)) >> (7 & e)) & n;
    },
    k = (t, e) => {
      const n = (e / 8) | 0;
      return (t[n] | (t[n + 1] << 8) | (t[n + 2] << 16)) >> (7 & e);
    },
    v = (t) => ((t + 7) / 8) | 0,
    x = (e, n, r) => (
      (null == n || n < 0) && (n = 0),
      (null == r || r > e.length) && (r = e.length),
      new t(e.subarray(n, r))
    ),
    E = [
      "unexpected EOF",
      "invalid block type",
      "invalid length/literal",
      "invalid distance",
      "stream finished",
      "no stream handler",
      ,
      "no callback",
      "invalid UTF-8 data",
      "extra field too long",
      "date not in range 1980-2099",
      "filename too long",
      "stream finishing",
      "invalid zip data",
    ],
    A = (t, e, n) => {
      const r = new Error(e || E[t]);
      if (
        ((r.code = t),
        Error.captureStackTrace && Error.captureStackTrace(r, A),
        !n)
      )
        throw r;
      return r;
    },
    T = (e, n, s, c) => {
      const a = e.length,
        h = c ? c.length : 0;
      if (!a || (n.f && !n.l)) return s || new t(0);
      const w = !s,
        g = w || 2 != n.i,
        b = n.i;
      w && (s = new t(3 * a));
      const m = (e) => {
        let n = s.length;
        if (e > n) {
          const r = new t(Math.max(2 * n, e));
          r.set(s), (s = r);
        }
      };
      let E = n.f || 0,
        T = n.p || 0,
        U = n.b || 0,
        z = n.l,
        F = n.d,
        S = n.m,
        I = n.n;
      const O = 8 * a;
      do {
        if (!z) {
          E = p(e, T, 1);
          const r = p(e, T + 1, 3);
          if (((T += 3), !r)) {
            const t = v(T) + 4,
              r = e[t - 4] | (e[t - 3] << 8),
              o = t + r;
            if (o > a) {
              b && A(0);
              break;
            }
            g && m(U + r),
              s.set(e.subarray(t, o), U),
              (n.b = U += r),
              (n.p = T = 8 * o),
              (n.f = E);
            continue;
          }
          if (1 == r) (z = d), (F = y), (S = 9), (I = 5);
          else if (2 == r) {
            const n = p(e, T, 31) + 257,
              r = p(e, T + 10, 15) + 4,
              o = n + p(e, T + 5, 31) + 1;
            T += 14;
            const s = new t(o),
              f = new t(19);
            for (let t = 0; t < r; ++t) f[l[t]] = p(e, T + 3 * t, 7);
            T += 3 * r;
            const c = M(f),
              i = (1 << c) - 1,
              a = u(f, c, 1);
            for (let t = 0; t < o; ) {
              const n = a[p(e, T, i)];
              T += 15 & n;
              const r = n >> 4;
              if (r < 16) s[t++] = r;
              else {
                let n = 0,
                  o = 0;
                for (
                  16 == r
                    ? ((o = 3 + p(e, T, 3)), (T += 2), (n = s[t - 1]))
                    : 17 == r
                    ? ((o = 3 + p(e, T, 7)), (T += 3))
                    : 18 == r && ((o = 11 + p(e, T, 127)), (T += 7));
                  o--;

                )
                  s[t++] = n;
              }
            }
            const h = s.subarray(0, n),
              w = s.subarray(n);
            (S = M(h)), (I = M(w)), (z = u(h, S, 1)), (F = u(w, I, 1));
          } else A(1);
          if (T > O) {
            b && A(0);
            break;
          }
        }
        g && m(U + 131072);
        const w = (1 << S) - 1,
          x = (1 << I) - 1;
        let j = T;
        for (; ; j = T) {
          const t = z[k(e, T) & w],
            n = t >> 4;
          if (((T += 15 & t), T > O)) {
            b && A(0);
            break;
          }
          if ((t || A(2), n < 256)) s[U++] = n;
          else {
            if (256 == n) {
              (j = T), (z = null);
              break;
            }
            {
              let t = n - 254;
              if (n > 264) {
                const o = n - 257,
                  l = r[o];
                (t = p(e, T, (1 << l) - 1) + f[o]), (T += l);
              }
              const l = F[k(e, T) & x],
                a = l >> 4;
              l || A(3), (T += 15 & l);
              let u = i[a];
              if (a > 3) {
                const t = o[a];
                (u += k(e, T) & ((1 << t) - 1)), (T += t);
              }
              if (T > O) {
                b && A(0);
                break;
              }
              g && m(U + 131072);
              const w = U + t;
              if (U < u) {
                const t = h - u,
                  e = Math.min(u, w);
                for (t + U < 0 && A(3); U < e; ++U) s[U] = c[t + U];
              }
              for (; U < w; ++U) s[U] = s[U - u];
            }
          }
        }
        (n.l = z),
          (n.p = j),
          (n.b = U),
          (n.f = E),
          z && ((E = 1), (n.m = S), (n.d = F), (n.n = I));
      } while (!E);
      return U != s.length && w ? x(s, 0, U) : s.subarray(0, U);
    },
    U = (t, e, n) => {
      n <<= 7 & e;
      const r = (e / 8) | 0;
      (t[r] |= n), (t[r + 1] |= n >> 8);
    },
    z = (t, e, n) => {
      n <<= 7 & e;
      const r = (e / 8) | 0;
      (t[r] |= n), (t[r + 1] |= n >> 8), (t[r + 2] |= n >> 16);
    },
    F = (n, r) => {
      const o = [];
      for (let t = 0; t < n.length; ++t) n[t] && o.push({ s: t, f: n[t] });
      const l = o.length,
        s = o.slice();
      if (!l) return { t: C, l: 0 };
      if (1 == l) {
        const e = new t(o[0].s + 1);
        return (e[o[0].s] = 1), { t: e, l: 1 };
      }
      o.sort((t, e) => t.f - e.f), o.push({ s: -1, f: 25001 });
      let f = o[0],
        c = o[1],
        i = 0,
        a = 1,
        h = 2;
      for (o[0] = { s: -1, f: f.f + c.f, l: f, r: c }; a != l - 1; )
        (f = o[o[i].f < o[h].f ? i++ : h++]),
          (c = o[i != a && o[i].f < o[h].f ? i++ : h++]),
          (o[a++] = { s: -1, f: f.f + c.f, l: f, r: c });
      let u = s[0].s;
      for (let t = 1; t < l; ++t) s[t].s > u && (u = s[t].s);
      const w = new e(u + 1);
      let g = S(o[a - 1], w, 0);
      if (g > r) {
        let t = 0,
          e = 0;
        const n = g - r,
          o = 1 << n;
        for (s.sort((t, e) => w[e.s] - w[t.s] || t.f - e.f); t < l; ++t) {
          const n = s[t].s;
          if (!(w[n] > r)) break;
          (e += o - (1 << (g - w[n]))), (w[n] = r);
        }
        for (e >>= n; e > 0; ) {
          const n = s[t].s;
          w[n] < r ? (e -= 1 << (r - w[n]++ - 1)) : ++t;
        }
        for (; t >= 0 && e; --t) {
          const n = s[t].s;
          w[n] == r && (--w[n], ++e);
        }
        g = r;
      }
      return { t: new t(w), l: g };
    },
    S = (t, e, n) =>
      -1 == t.s ? Math.max(S(t.l, e, n + 1), S(t.r, e, n + 1)) : (e[t.s] = n),
    I = (t) => {
      let n = t.length;
      for (; n && !t[--n]; );
      const r = new e(++n);
      let o = 0,
        l = t[0],
        s = 1;
      const f = (t) => {
        r[o++] = t;
      };
      for (let e = 1; e <= n; ++e)
        if (t[e] == l && e != n) ++s;
        else {
          if (!l && s > 2) {
            for (; s > 138; s -= 138) f(32754);
            s > 2 &&
              (f(s > 10 ? ((s - 11) << 5) | 28690 : ((s - 3) << 5) | 12305),
              (s = 0));
          } else if (s > 3) {
            for (f(l), --s; s > 6; s -= 6) f(8304);
            s > 2 && (f(((s - 3) << 5) | 8208), (s = 0));
          }
          for (; s--; ) f(l);
          (s = 1), (l = t[e]);
        }
      return { c: r.subarray(0, o), n: n };
    },
    O = (t, e) => {
      let n = 0;
      for (let r = 0; r < e.length; ++r) n += t[r] * e[r];
      return n;
    },
    j = (t, e, n) => {
      const r = n.length,
        o = v(e + 2);
      (t[o] = 255 & r),
        (t[o + 1] = r >> 8),
        (t[o + 2] = 255 ^ t[o]),
        (t[o + 3] = 255 ^ t[o + 1]);
      for (let e = 0; e < r; ++e) t[o + e + 4] = n[e];
      return 8 * (o + 4 + r);
    },
    q = (t, n, s, f, c, i, a, h, d, y, M) => {
      U(n, M++, s), ++c[256];
      const { t: p, l: k } = F(c, 15),
        { t: v, l: x } = F(i, 15),
        { c: E, n: A } = I(p),
        { c: T, n: S } = I(v),
        q = new e(19);
      for (let t = 0; t < E.length; ++t) ++q[31 & E[t]];
      for (let t = 0; t < T.length; ++t) ++q[31 & T[t]];
      const { t: B, l: C } = F(q, 7);
      let D = 19;
      for (; D > 4 && !B[l[D - 1]]; --D);
      const G = (y + 5) << 3,
        H = O(c, w) + O(i, g) + a,
        J =
          O(c, p) +
          O(i, v) +
          a +
          14 +
          3 * D +
          O(q, B) +
          2 * q[16] +
          3 * q[17] +
          7 * q[18];
      if (d >= 0 && G <= H && G <= J) return j(n, M, t.subarray(d, d + y));
      let K, L, N, P;
      if ((U(n, M, 1 + (J < H)), (M += 2), J < H)) {
        (K = u(p, k, 0)), (L = p), (N = u(v, x, 0)), (P = v);
        const t = u(B, C, 0);
        U(n, M, A - 257), U(n, M + 5, S - 1), U(n, M + 10, D - 4), (M += 14);
        for (let t = 0; t < D; ++t) U(n, M + 3 * t, B[l[t]]);
        M += 3 * D;
        const e = [E, T];
        for (let r = 0; r < 2; ++r) {
          const o = e[r];
          for (let e = 0; e < o.length; ++e) {
            const r = 31 & o[e];
            U(n, M, t[r]),
              (M += B[r]),
              r > 15 && (U(n, M, (o[e] >> 5) & 127), (M += o[e] >> 12));
          }
        }
      } else (K = b), (L = w), (N = m), (P = g);
      for (let t = 0; t < h; ++t) {
        const e = f[t];
        if (e > 255) {
          const t = (e >> 18) & 31;
          z(n, M, K[t + 257]),
            (M += L[t + 257]),
            t > 7 && (U(n, M, (e >> 23) & 31), (M += r[t]));
          const l = 31 & e;
          z(n, M, N[l]),
            (M += P[l]),
            l > 3 && (z(n, M, (e >> 5) & 8191), (M += o[l]));
        } else z(n, M, K[e]), (M += L[e]);
      }
      return z(n, M, K[256]), M + L[256];
    },
    B = new n([
      65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632,
    ]),
    C = new t(0),
    D = (l, s, f, i, h, u) => {
      const w = u.z || l.length,
        g = new t(i + w + 5 * (1 + Math.ceil(w / 7e3)) + h),
        b = g.subarray(i, g.length - h),
        d = u.l;
      let m = 7 & (u.r || 0);
      if (s) {
        m && (b[0] = u.r >> 3);
        const t = B[s - 1],
          i = t >> 13,
          h = 8191 & t,
          g = (1 << f) - 1,
          y = u.p || new e(32768),
          M = u.h || new e(g + 1),
          p = Math.ceil(f / 3),
          k = 2 * p,
          v = (t) => (l[t] ^ (l[t + 1] << p) ^ (l[t + 2] << k)) & g,
          x = new n(25e3),
          E = new e(288),
          A = new e(32);
        let T = 0,
          U = 0,
          z = u.i || 0,
          F = 0,
          S = u.w || 0,
          I = 0;
        for (; z + 2 < w; ++z) {
          const t = v(z);
          let e = 32767 & z,
            n = M[t];
          if (((y[e] = n), (M[t] = e), S <= z)) {
            const s = w - z;
            if ((T > 7e3 || F > 24576) && (s > 423 || !d)) {
              (m = q(l, b, 0, x, E, A, U, F, I, z - I, m)),
                (F = T = U = 0),
                (I = z);
              for (let t = 0; t < 286; ++t) E[t] = 0;
              for (let t = 0; t < 30; ++t) A[t] = 0;
            }
            let f = 2,
              u = 0,
              g = h,
              M = (e - n) & 32767;
            if (s > 2 && t == v(z - M)) {
              const t = Math.min(i, s) - 1,
                r = Math.min(32767, z),
                o = Math.min(258, s);
              for (; M <= r && --g && e != n; ) {
                if (l[z + f] == l[z + f - M]) {
                  let e = 0;
                  for (; e < o && l[z + e] == l[z + e - M]; ++e);
                  if (e > f) {
                    if (((f = e), (u = M), e > t)) break;
                    const r = Math.min(M, e - 2);
                    let o = 0;
                    for (let t = 0; t < r; ++t) {
                      const e = (z - M + t) & 32767,
                        r = (e - y[e]) & 32767;
                      r > o && ((o = r), (n = e));
                    }
                  }
                }
                (e = n), (n = y[e]), (M += (e - n) & 32767);
              }
            }
            if (u) {
              x[F++] = 268435456 | (c[f] << 18) | a[u];
              const t = 31 & c[f],
                e = 31 & a[u];
              (U += r[t] + o[e]), ++E[257 + t], ++A[e], (S = z + f), ++T;
            } else (x[F++] = l[z]), ++E[l[z]];
          }
        }
        for (z = Math.max(z, S); z < w; ++z) (x[F++] = l[z]), ++E[l[z]];
        (m = q(l, b, d, x, E, A, U, F, I, z - I, m)),
          d ||
            ((u.r = (7 & m) | (b[(m / 8) | 0] << 3)),
            (m -= 7),
            (u.h = M),
            (u.p = y),
            (u.i = z),
            (u.w = S));
      } else {
        for (let t = u.w || 0; t < w + d; t += 65535) {
          let e = t + 65535;
          e >= w && ((b[(m / 8) | 0] = d), (e = w)),
            (m = j(b, m + 1, l.subarray(t, e)));
        }
        u.i = w;
      }
      return x(g, 0, i + v(m) + h);
    },
    G = (e, n, r, o, l) => {
      if (!l && ((l = { l: 1 }), n.dictionary)) {
        const r = n.dictionary.subarray(-32768),
          o = new t(r.length + e.length);
        o.set(r), o.set(e, r.length), (e = o), (l.w = r.length);
      }
      return D(
        e,
        null == n.level ? 6 : n.level,
        null == n.mem
          ? l.l
            ? Math.ceil(1.5 * Math.max(8, Math.min(13, Math.log(e.length))))
            : 20
          : 12 + n.mem,
        r,
        o,
        l
      );
    };
  function deflateSync(t, e) {
    return G(t, e || {}, 0, 0);
  }
  function inflateSync(t, e) {
    return T(t, { i: 2 }, e && e.out, e && e.dictionary);
  }
}
// from markdown-it-texmath@1.0.1
function escapeHTML(e) {
  return e
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&apos;")
    .replace(/\//g, "&sol;");
}
function texmath(e, t) {
  const n = texmath.mergeDelimiters(t && t.delimiters),
    a = (t && t.outerSpace) || !1,
    o = (t && t.katexOptions) || {};
  (o.throwOnError = o.throwOnError || !1),
    (o.macros = o.macros || (t && t.macros)),
    texmath.katex ||
      (t && "object" == typeof t.engine
        ? (texmath.katex = t.engine)
        : "object" == typeof module
        ? (texmath.katex = require("katex"))
        : (texmath.katex = {
            renderToString: () => "No math renderer found.",
          }));
  for (const t of n.inline)
    a && "outerSpace" in t && (t.outerSpace = !0),
      e.inline.ruler.before("escape", t.name, texmath.inline(t)),
      (e.renderer.rules[t.name] = (e, n) =>
        t.tmpl.replace(
          /\$1/,
          texmath.render(e[n].content, !!t.displayMode, o)
        ));
  for (const t of n.block)
    e.block.ruler.before("fence", t.name, texmath.block(t)),
      (e.renderer.rules[t.name] = (e, n) =>
        t.tmpl
          .replace(/\$2/, escapeHTML(e[n].info))
          .replace(/\$1/, texmath.render(e[n].content, !0, o)));
}
(texmath.mergeDelimiters = function (e) {
  const t = Array.isArray(e) ? e : "string" == typeof e ? [e] : ["dollars"],
    n = { inline: [], block: [] };
  for (const e of t)
    e in texmath.rules &&
      (n.inline.push(...texmath.rules[e].inline),
      n.block.push(...texmath.rules[e].block));
  return n;
}),
  (texmath.inline = (e) =>
    function (t, n) {
      const a = t.pos,
        o = t.src,
        r =
          o.startsWith(e.tag, (e.rex.lastIndex = a)) &&
          (!e.pre || e.pre(o, e.outerSpace, a)) &&
          e.rex.exec(o),
        s =
          !!r &&
          a < e.rex.lastIndex &&
          (!e.post || e.post(o, e.outerSpace, e.rex.lastIndex - 1));
      if (s) {
        if (!n) {
          const n = t.push(e.name, "math", 0);
          (n.content = r[1]), (n.markup = e.tag);
        }
        t.pos = e.rex.lastIndex;
      }
      return s;
    }),
  (texmath.block = (e) =>
    function (t, n, a, o) {
      const r = t.bMarks[n] + t.tShift[n],
        s = t.src,
        m =
          s.startsWith(e.tag, (e.rex.lastIndex = r)) &&
          (!e.pre || e.pre(s, !1, r)) &&
          e.rex.exec(s),
        l =
          !!m &&
          r < e.rex.lastIndex &&
          (!e.post || e.post(s, !1, e.rex.lastIndex - 1));
      if (l && !o) {
        const o = e.rex.lastIndex - 1;
        let r;
        for (
          r = n;
          r < a && !(o >= t.bMarks[r] + t.tShift[r] && o <= t.eMarks[r]);
          r++
        );
        const s = t.lineMax,
          l = t.parentType;
        (t.lineMax = r),
          (t.parentType = "math"),
          "blockquote" === l && (m[1] = m[1].replace(/(\n*?^(?:\s*>)+)/gm, ""));
        let c = t.push(e.name, "math", 0);
        (c.block = !0),
          (c.tag = e.tag),
          (c.markup = ""),
          (c.content = m[1]),
          (c.info = m[m.length - 1]),
          (c.map = [n, r + 1]),
          (t.parentType = l),
          (t.lineMax = s),
          (t.line = r + 1);
      }
      return l;
    }),
  (texmath.render = function (e, t, n) {
    let a;
    n.displayMode = t;
    try {
      a = texmath.katex.renderToString(e, n);
    } catch (t) {
      a = escapeHTML(`${e}:${t.message}`);
    }
    return a;
  }),
  (texmath.inlineRuleNames = ["math_inline", "math_inline_double"]),
  (texmath.blockRuleNames = ["math_block", "math_block_eqno"]),
  (texmath.$_pre = (e, t, n) => {
    const a = n > 0 && e[n - 1].charCodeAt(0);
    return t ? !a || 32 === a : !a || (92 !== a && (a < 48 || a > 57));
  }),
  (texmath.$_post = (e, t, n) => {
    const a = e[n + 1] && e[n + 1].charCodeAt(0);
    return t
      ? !a || 32 === a || 46 === a || 44 === a || 59 === a
      : !a || a < 48 || a > 57;
  }),
  (texmath.rules = {
    brackets: {
      inline: [
        {
          name: "math_inline",
          rex: /\\\((.+?)\\\)/gy,
          tmpl: "<eq>$1</eq>",
          tag: "\\(",
        },
      ],
      block: [
        {
          name: "math_block_eqno",
          rex: /\\\[(((?!\\\]|\\\[)[\s\S])+?)\\\]\s*?\(([^)$\r\n]+?)\)/gmy,
          tmpl: '<section class="eqno"><eqn>$1</eqn><span>($2)</span></section>',
          tag: "\\[",
        },
        {
          name: "math_block",
          rex: /\\\[([\s\S]+?)\\\]/gmy,
          tmpl: "<section><eqn>$1</eqn></section>",
          tag: "\\[",
        },
      ],
    },
    doxygen: {
      inline: [
        {
          name: "math_inline",
          rex: /\\f\$(.+?)\\f\$/gy,
          tmpl: "<eq>$1</eq>",
          tag: "\\f$",
        },
      ],
      block: [
        {
          name: "math_block_eqno",
          rex: /\\f\[([^]+?)\\f\]\s*?\(([^)\s]+?)\)/gmy,
          tmpl: '<section class="eqno"><eqn>$1</eqn><span>($2)</span></section>',
          tag: "\\f[",
        },
        {
          name: "math_block",
          rex: /\\f\[([^]+?)\\f\]/gmy,
          tmpl: "<section><eqn>$1</eqn></section>",
          tag: "\\f[",
        },
      ],
    },
    gitlab: {
      inline: [
        {
          name: "math_inline",
          rex: /\$`(.+?)`\$/gy,
          tmpl: "<eq>$1</eq>",
          tag: "$`",
        },
      ],
      block: [
        {
          name: "math_block_eqno",
          rex: /`{3}math\s*([^`]+?)\s*?`{3}\s*\(([^)\r\n]+?)\)/gm,
          tmpl: '<section class="eqno"><eqn>$1</eqn><span>($2)</span></section>',
          tag: "```math",
        },
        {
          name: "math_block",
          rex: /`{3}math\s*([^`]*?)\s*`{3}/gm,
          tmpl: "<section><eqn>$1</eqn></section>",
          tag: "```math",
        },
      ],
    },
    julia: {
      inline: [
        {
          name: "math_inline",
          rex: /`{2}([^`]+?)`{2}/gy,
          tmpl: "<eq>$1</eq>",
          tag: "``",
        },
        {
          name: "math_inline",
          rex: /\$((?:\S?)|(?:\S.*?\S))\$/gy,
          tmpl: "<eq>$1</eq>",
          tag: "$",
          spaceEnclosed: !1,
          pre: texmath.$_pre,
          post: texmath.$_post,
        },
      ],
      block: [
        {
          name: "math_block_eqno",
          rex: /`{3}math\s+?([^`]+?)\s+?`{3}\s*?\(([^)$\r\n]+?)\)/gmy,
          tmpl: '<section class="eqno"><eqn>$1</eqn><span>($2)</span></section>',
          tag: "```math",
        },
        {
          name: "math_block",
          rex: /`{3}math\s+?([^`]+?)\s+?`{3}/gmy,
          tmpl: "<section><eqn>$1</eqn></section>",
          tag: "```math",
        },
      ],
    },
    kramdown: {
      inline: [
        {
          name: "math_inline",
          rex: /\${2}(.+?)\${2}/gy,
          tmpl: "<eq>$1</eq>",
          tag: "$$",
        },
      ],
      block: [
        {
          name: "math_block_eqno",
          rex: /\${2}([^$]+?)\${2}\s*?\(([^)\s]+?)\)/gmy,
          tmpl: '<section class="eqno"><eqn>$1</eqn><span>($2)</span></section>',
          tag: "$$",
        },
        {
          name: "math_block",
          rex: /\${2}([^$]+?)\${2}/gmy,
          tmpl: "<section><eqn>$1</eqn></section>",
          tag: "$$",
        },
      ],
    },
    beg_end: {
      inline: [],
      block: [
        {
          name: "math_block",
          rex: /(\\(?:begin)\{([a-z]+)\}[\s\S]+?\\(?:end)\{\2\})/gmy,
          tmpl: "<section><eqn>$1</eqn></section>",
          tag: "\\",
        },
      ],
    },
    dollars: {
      inline: [
        {
          name: "math_inline_double",
          rex: /\${2}([^$]*?[^\\])\${2}/gy,
          tmpl: "<section><eqn>$1</eqn></section>",
          tag: "$$",
          displayMode: !0,
          pre: texmath.$_pre,
          post: texmath.$_post,
        },
        {
          name: "math_inline",
          rex: /\$((?:[^\s\\])|(?:\S.*?[^\s\\]))\$/gy,
          tmpl: "<eq>$1</eq>",
          tag: "$",
          outerSpace: !1,
          pre: texmath.$_pre,
          post: texmath.$_post,
        },
      ],
      block: [
        {
          name: "math_block_eqno",
          rex: /\${2}([^$]*?[^\\])\${2}\s*?\(([^)\s]+?)\)/gmy,
          tmpl: '<section class="eqno"><eqn>$1</eqn><span>($2)</span></section>',
          tag: "$$",
        },
        {
          name: "math_block",
          rex: /\${2}([^$]*?[^\\])\${2}/gmy,
          tmpl: "<section><eqn>$1</eqn></section>",
          tag: "$$",
        },
      ],
    },
  });
// from morphdom@2.7.2
{
  let e,
    t = 11,
    l = (e, l) => {
      let n,
        i,
        r,
        d,
        o,
        a = l.attributes;
      if (l.nodeType === t || e.nodeType === t) return;
      for (let t = a.length - 1; t >= 0; t--)
        (n = a[t]),
          (i = n.name),
          (r = n.namespaceURI),
          (d = n.value),
          r
            ? ((i = n.localName || i),
              (o = e.getAttributeNS(r, i)),
              o !== d &&
                ("xmlns" === n.prefix && (i = n.name),
                e.setAttributeNS(r, i, d)))
            : ((o = e.getAttribute(i)), o !== d && e.setAttribute(i, d));
      let u = e.attributes;
      for (let t = u.length - 1; t >= 0; t--)
        (n = u[t]),
          (i = n.name),
          (r = n.namespaceURI),
          r
            ? ((i = n.localName || i),
              l.hasAttributeNS(r, i) || e.removeAttributeNS(r, i))
            : l.hasAttribute(i) || e.removeAttribute(i);
    },
    n = "http://www.w3.org/1999/xhtml",
    i = "undefined" == typeof document ? void 0 : document,
    r = !!i && "content" in i.createElement("template"),
    d = !!i && i.createRange && "createContextualFragment" in i.createRange(),
    o = (e) => {
      let t = i.createElement("template");
      return (t.innerHTML = e), t.content.childNodes[0];
    },
    a = (t) => (
      e || ((e = i.createRange()), e.selectNode(i.body)),
      e.createContextualFragment(t).childNodes[0]
    ),
    u = (e) => {
      let t = i.createElement("body");
      return (t.innerHTML = e), t.childNodes[0];
    },
    s = (e) => ((e = e.trim()), r ? o(e) : d ? a(e) : u(e)),
    f = (e, t) => {
      let l,
        n,
        i = e.nodeName,
        r = t.nodeName;
      return (
        i === r ||
        ((l = i.charCodeAt(0)),
        (n = r.charCodeAt(0)),
        l <= 90 && n >= 97
          ? i === r.toUpperCase()
          : n <= 90 && l >= 97 && r === i.toUpperCase())
      );
    },
    c = (e, t) => (t && t !== n ? i.createElementNS(t, e) : i.createElement(e)),
    m = (e, t) => {
      let l = e.firstChild;
      for (; l; ) {
        let e = l.nextSibling;
        t.appendChild(l), (l = e);
      }
      return t;
    },
    p = (e, t, l) => {
      e[l] !== t[l] &&
        ((e[l] = t[l]), e[l] ? e.setAttribute(l, "") : e.removeAttribute(l));
    },
    h = {
      OPTION: (e, t) => {
        let l = e.parentNode;
        if (l) {
          let n = l.nodeName.toUpperCase();
          "OPTGROUP" === n &&
            ((l = l.parentNode), (n = l && l.nodeName.toUpperCase())),
            "SELECT" !== n ||
              l.hasAttribute("multiple") ||
              (e.hasAttribute("selected") &&
                !t.selected &&
                (e.setAttribute("selected", "selected"),
                e.removeAttribute("selected")),
              (l.selectedIndex = -1));
        }
        p(e, t, "selected");
      },
      INPUT: (e, t) => {
        p(e, t, "checked"),
          p(e, t, "disabled"),
          e.value !== t.value && (e.value = t.value),
          t.hasAttribute("value") || e.removeAttribute("value");
      },
      TEXTAREA: (e, t) => {
        let l = t.value;
        e.value !== l && (e.value = l);
        let n = e.firstChild;
        if (n) {
          let t = n.nodeValue;
          if (t == l || (!l && t == e.placeholder)) return;
          n.nodeValue = l;
        }
      },
      SELECT: (e, t) => {
        if (!t.hasAttribute("multiple")) {
          let t,
            l,
            n = -1,
            i = 0,
            r = e.firstChild;
          for (; r; )
            if (
              ((l = r.nodeName && r.nodeName.toUpperCase()), "OPTGROUP" === l)
            )
              (t = r), (r = t.firstChild);
            else {
              if ("OPTION" === l) {
                if (r.hasAttribute("selected")) {
                  n = i;
                  break;
                }
                i++;
              }
              (r = r.nextSibling), !r && t && ((r = t.nextSibling), (t = null));
            }
          e.selectedIndex = n;
        }
      },
    },
    N = 1,
    b = 11,
    A = 3,
    C = 8,
    T = () => {},
    g = (e) => {
      if (e) return (e.getAttribute && e.getAttribute("id")) || e.id;
    },
    E = (e) =>
      function (t, l, n) {
        if ((n || (n = {}), "string" == typeof l))
          if (
            "#document" === t.nodeName ||
            "HTML" === t.nodeName ||
            "BODY" === t.nodeName
          ) {
            let e = l;
            (l = i.createElement("html")).innerHTML = e;
          } else l = s(l);
        else l.nodeType === b && (l = l.firstElementChild);
        let r = n.getNodeKey || g,
          d = n.onBeforeNodeAdded || T,
          o = n.onNodeAdded || T,
          a = n.onBeforeElUpdated || T,
          u = n.onElUpdated || T,
          p = n.onBeforeNodeDiscarded || T,
          E = n.onNodeDiscarded || T,
          v = n.onBeforeElChildrenUpdated || T,
          x = n.skipFromChildren || T,
          y =
            n.addChild ||
            function (e, t) {
              return e.appendChild(t);
            },
          S = !0 === n.childrenOnly,
          U = Object.create(null),
          O = [],
          R = (e) => {
            O.push(e);
          },
          V = (e, t) => {
            if (e.nodeType === N) {
              let l = e.firstChild;
              for (; l; ) {
                let e;
                t && (e = r(l)) ? R(e) : (E(l), l.firstChild && V(l, t)),
                  (l = l.nextSibling);
              }
            }
          },
          w = (e, t, l) => {
            !1 !== p(e) && (t && t.removeChild(e), E(e), V(e, l));
          },
          I = (e) => {
            if (e.nodeType === N || e.nodeType === b) {
              let t = e.firstChild;
              for (; t; ) {
                let e = r(t);
                e && (U[e] = t), I(t), (t = t.nextSibling);
              }
            }
          };
        I(t);
        let P = (e) => {
            o(e);
            let t = e.firstChild;
            for (; t; ) {
              let e = t.nextSibling,
                l = r(t);
              if (l) {
                let e = U[l];
                e && f(t, e)
                  ? (t.parentNode.replaceChild(e, t), B(e, t))
                  : P(t);
              } else P(t);
              t = e;
            }
          },
          B = (t, l, n) => {
            let i = r(l);
            if ((i && delete U[i], !n)) {
              if (!1 === a(t, l)) return;
              if ((e(t, l), u(t), !1 === v(t, l))) return;
            }
            "TEXTAREA" !== t.nodeName ? L(t, l) : h.TEXTAREA(t, l);
          },
          L = (e, t) => {
            let l,
              n,
              o,
              a,
              u,
              s = x(e, t),
              c = t.firstChild,
              m = e.firstChild;
            e: for (; c; ) {
              for (a = c.nextSibling, l = r(c); !s && m; ) {
                if (((o = m.nextSibling), c.isEqualNode && c.isEqualNode(m))) {
                  (c = a), (m = o);
                  continue e;
                }
                n = r(m);
                let t,
                  i = m.nodeType;
                if (
                  (i === c.nodeType &&
                    (i === N
                      ? (l
                          ? l !== n &&
                            ((u = U[l])
                              ? o === u
                                ? (t = !1)
                                : (e.insertBefore(u, m),
                                  n ? R(n) : w(m, e, !0),
                                  (m = u),
                                  (n = r(m)))
                              : (t = !1))
                          : n && (t = !1),
                        (t = !1 !== t && f(m, c)),
                        t && B(m, c))
                      : (i !== A && i != C) ||
                        ((t = !0),
                        m.nodeValue !== c.nodeValue &&
                          (m.nodeValue = c.nodeValue))),
                  t)
                ) {
                  (c = a), (m = o);
                  continue e;
                }
                n ? R(n) : w(m, e, !0), (m = o);
              }
              if (l && (u = U[l]) && f(u, c)) s || y(e, u), B(u, c);
              else {
                let t = d(c);
                !1 !== t &&
                  (t && (c = t),
                  c.actualize && (c = c.actualize(e.ownerDocument || i)),
                  y(e, c),
                  P(c));
              }
              (c = a), (m = o);
            }
            ((e, t, l) => {
              for (; t; ) {
                let n = t.nextSibling;
                (l = r(t)) ? R(l) : w(t, e, !0), (t = n);
              }
            })(e, m, n);
            let p = h[e.nodeName];
            p && p(e, t);
          },
          D = t,
          z = D.nodeType,
          H = l.nodeType;
        if (!S)
          if (z === N)
            H === N
              ? f(t, l) || (E(t), (D = m(t, c(l.nodeName, l.namespaceURI))))
              : (D = l);
          else if (z === A || z === C) {
            if (H === z)
              return (
                D.nodeValue !== l.nodeValue && (D.nodeValue = l.nodeValue), D
              );
            D = l;
          }
        if ((B(D, l, S), O))
          for (let e = 0, t = O.length; e < t; e++) {
            let t = U[O[e]];
            t && w(t, t.parentNode, !1);
          }
        return (
          !S &&
            D !== t &&
            t.parentNode &&
            (D.actualize && (D = D.actualize(t.ownerDocument || i)),
            t.parentNode.replaceChild(D, t)),
          D
        );
      };
  window.morphdom = E(l);
}
// from sha1-uint8array@0.10.7
{
  const K = [1518500249, 1859775393, -1894007588, -899497514];
  window.createHash = () => new Hash();
  class Hash {
    constructor() {
      (this.A = 1732584193),
        (this.B = -271733879),
        (this.C = -1732584194),
        (this.D = 271733878),
        (this.E = -1009589776),
        (this._size = 0),
        (this._sp = 0),
        (!sharedBuffer || sharedOffset >= 8e3) &&
          ((sharedBuffer = new ArrayBuffer(8e3)), (sharedOffset = 0)),
        (this._byte = new Uint8Array(sharedBuffer, sharedOffset, 80)),
        (this._word = new Int32Array(sharedBuffer, sharedOffset, 20)),
        (sharedOffset += 80);
    }
    update(t) {
      if ("string" == typeof t) return this._utf8(t);
      if (null == t) throw new TypeError("Invalid type: " + typeof t);
      const s = t.byteOffset,
        e = t.byteLength;
      let r = (e / 64) | 0,
        i = 0;
      if (r && !(3 & s) && !(this._size % 64)) {
        const e = new Int32Array(t.buffer, s, 16 * r);
        for (; r--; ) this._int32(e, i >> 2), (i += 64);
        this._size += i;
      }
      if (1 !== t.BYTES_PER_ELEMENT && t.buffer) {
        const r = new Uint8Array(t.buffer, s + i, e - i);
        return this._uint8(r);
      }
      return i === e ? this : this._uint8(t, i);
    }
    _uint8(t, s) {
      const { _byte: e, _word: r } = this,
        i = t.length;
      for (s |= 0; s < i; ) {
        const h = this._size % 64;
        let n = h;
        for (; s < i && n < 64; ) e[n++] = t[s++];
        n >= 64 && this._int32(r), (this._size += n - h);
      }
      return this;
    }
    _utf8(t) {
      const { _byte: s, _word: e } = this,
        r = t.length;
      let i = this._sp;
      for (let h = 0; h < r; ) {
        const n = this._size % 64;
        let f = n;
        for (; h < r && f < 64; ) {
          let e = 0 | t.charCodeAt(h++);
          e < 128
            ? (s[f++] = e)
            : e < 2048
            ? ((s[f++] = 192 | (e >>> 6)), (s[f++] = 128 | (63 & e)))
            : e < 55296 || e > 57343
            ? ((s[f++] = 224 | (e >>> 12)),
              (s[f++] = 128 | ((e >>> 6) & 63)),
              (s[f++] = 128 | (63 & e)))
            : i
            ? ((e = ((1023 & i) << 10) + (1023 & e) + 65536),
              (s[f++] = 240 | (e >>> 18)),
              (s[f++] = 128 | ((e >>> 12) & 63)),
              (s[f++] = 128 | ((e >>> 6) & 63)),
              (s[f++] = 128 | (63 & e)),
              (i = 0))
            : (i = e);
        }
        f >= 64 && (this._int32(e), (e[0] = e[16])), (this._size += f - n);
      }
      return (this._sp = i), this;
    }
    _int32(t, s) {
      let { A: e, B: r, C: i, D: h, E: n } = this,
        f = 0;
      for (s |= 0; f < 16; ) W[f++] = swap32(t[s++]);
      for (f = 16; f < 80; f++)
        W[f] = rotate1(W[f - 3] ^ W[f - 8] ^ W[f - 14] ^ W[f - 16]);
      for (f = 0; f < 80; f++) {
        const t = (f / 20) | 0,
          s = (rotate5(e) + ft(t, r, i, h) + n + W[f] + K[t]) | 0;
        (n = h), (h = i), (i = rotate30(r)), (r = e), (e = s);
      }
      (this.A = (e + this.A) | 0),
        (this.B = (r + this.B) | 0),
        (this.C = (i + this.C) | 0),
        (this.D = (h + this.D) | 0),
        (this.E = (n + this.E) | 0);
    }
    digest(t) {
      const { _byte: s, _word: e } = this;
      let r = this._size % 64 | 0;
      for (s[r++] = 128; 3 & r; ) s[r++] = 0;
      if (((r >>= 2), r > 14)) {
        for (; r < 16; ) e[r++] = 0;
        (r = 0), this._int32(e);
      }
      for (; r < 16; ) e[r++] = 0;
      const i = 8 * this._size,
        h = (4294967295 & i) >>> 0,
        n = (i - h) / 4294967296;
      return (
        n && (e[14] = swap32(n)),
        h && (e[15] = swap32(h)),
        this._int32(e),
        "hex" === t ? this._hex() : this._bin()
      );
    }
    _hex() {
      const { A: t, B: s, C: e, D: r, E: i } = this;
      return hex32(t) + hex32(s) + hex32(e) + hex32(r) + hex32(i);
    }
    _bin() {
      const { A: t, B: s, C: e, D: r, E: i, _byte: h, _word: n } = this;
      return (
        (n[0] = swap32(t)),
        (n[1] = swap32(s)),
        (n[2] = swap32(e)),
        (n[3] = swap32(r)),
        (n[4] = swap32(i)),
        h.slice(0, 20)
      );
    }
  }
  const W = new Int32Array(80);
  let sharedBuffer,
    sharedOffset = 0;
  const hex32 = (t) => (t + 4294967296).toString(16).substr(-8),
    swapLE = (t) =>
      ((t << 24) & 4278190080) |
      ((t << 8) & 16711680) |
      ((t >> 8) & 65280) |
      ((t >> 24) & 255),
    swapBE = (t) => t,
    swap32 = isBE() ? swapBE : swapLE,
    rotate1 = (t) => (t << 1) | (t >>> 31),
    rotate5 = (t) => (t << 5) | (t >>> 27),
    rotate30 = (t) => (t << 30) | (t >>> 2);
  function ft(t, s, e, r) {
    return 0 === t
      ? (s & e) | (~s & r)
      : 2 === t
      ? (s & e) | (s & r) | (e & r)
      : s ^ e ^ r;
  }
  function isBE() {
    return 254 === new Uint8Array(new Uint16Array([65279]).buffer)[0];
  }
}
const stringToArray = (string) => {
  let arr = [];
  for (let i = 0; i < string.length; i++) {
    arr.push(string.charCodeAt(i));
  }
  return arr;
};
const stringToUint = (string) => {
  return new Uint8Array(stringToArray(string));
};
const uintToString = (uintArray) => {
  let str = "";
  let len = Math.ceil(uintArray.byteLength / 32767);
  for (let i = 0; i < len; i++) {
    str += String.fromCharCode.apply(
      null,
      uintArray.subarray(
        i * 32767,
        Math.min((i + 1) * 32767, uintArray.byteLength)
      )
    );
  }
  return str;
};
let isCompressedChats = localStorage.getItem("compressedChats") === "true";
const originSetItem = localStorage.setItem;
localStorage.setItem = (key, value) => {
  try {
    if (isCompressedChats && key === "chats")
      value = uintToString(
        deflateSync(new TextEncoder().encode(value), { level: 1 })
      );
    originSetItem.call(localStorage, key, value);
  } catch (e) {
    if (isCompressedChats) {
      notyf.error(translations[locale]["localQuotaExceedTip"]);
      return;
    }
    let isKeyChats = key === "chats";
    let compressed = uintToString(
      deflateSync(
        new TextEncoder().encode(
          isKeyChats ? value : localStorage.getItem("chats")
        ),
        { level: 1 }
      )
    );
    originSetItem.call(localStorage, "chats", compressed);
    originSetItem.call(localStorage, "compressedChats", true);
    isCompressedChats = true;
    if (!isKeyChats) originSetItem.call(localStorage, key, value);
  }
};

const localeList = ["en", "zh"];
let locale; // UI语言
const setLangEle = document.getElementById("setLang");
const setLang = () => {
  let langClass = locale + "Lang";
  localStorage.setItem("UILang", locale);
  document.documentElement.lang = locale === "zh" ? "zh-CN" : "en";
  setLangEle.classList = "setDetail themeDetail langDetail " + langClass;
};
setLangEle.onclick = (ev) => {
  let idx = Array.prototype.indexOf.call(setLangEle.children, ev.target);
  if (locale !== localeList[idx]) {
    locale = localeList[idx];
    setLang();
    changeLocale();
  }
};
const initLang = () => {
  let localLang =
    localStorage.getItem("UILang") ||
    (navigator.language.startsWith("zh-") ? "zh" : "en");
  let isInit = locale === void 0;
  if (locale !== localLang) {
    locale = localLang;
    if (!isInit) changeLocale();
  }
  setLang();
};
initLang();
const translations = {
  en: {
    description: "Simple and powerful ChatGPT app",
    newChat: "New chat",
    newChatName: "New chat",
    newFolder: "New folder",
    newFolderName: "New folder",
    search: "Search",
    matchCaseTip: "Match case",
    forceRe: "Force refresh",
    clearAll: "Clear all chats",
    setting: "Setting",
    nav: "Navigate",
    winedWin: "Window",
    fullWin: "Full screen",
    quickSet: "Quick setting",
    chat: "Chat",
    tts: "TTS",
    stt: "STT",
    avatar: "Avatar",
    systemRole: "System role",
    presetRole: "Preset",
    default: "Default",
    assistant: "Assistant",
    cat: "Cat girl",
    emoji: "Emoji",
    withImg: "Image",
    defaultText: "",
    assistantText:
      "You are a helpful assistant, answer as concisely as possible.",
    catText: "You are a cute cat girl, you must end every sentence with 'meow'",
    emojiText:
      "Your personality is very lively, there must be at least one emoji icon in every sentence",
    imageText:
      "When you need to send pictures, please generate them in markdown language, without backslashes or code boxes. When you need to use the unsplash API, follow the format, https://source.unsplash.com/960x640/?<English keywords>",
    nature: "Nature",
    natureNeg: "Accurate",
    naturePos: "Creativity",
    quality: "Quality",
    qualityNeg: "Repetitive",
    qualityPos: "Nonsense",
    chatsWidth: "Chats width",
    typeSpeed: "Typing speed",
    continuousLen: "Context messages",
    msgAbbr: " msgs.",
    slow: "Slow",
    fast: "Fast",
    longReply: "Long reply",
    ttsService: "TTS API",
    sttService: "STT API",
    openaiTTS: "OpenAI",
    azureTTS: "Azure",
    edgeTTS: "Edge",
    systemTTS: "System",
    azureRegion: "Azure region",
    azureKey: "Azure key",
    loadVoice: "Load voice",
    voiceName: "Switch",
    userVoice: "User voice",
    replyVoice: "Reply voice",
    TTSTest: "Hello, nice to meet you.",
    play: "Play",
    pause: "Pause",
    resume: "Resume",
    stop: "Stop",
    style: "Style",
    role: "Role",
    volume: "Volume",
    low: "Low",
    high: "High",
    rate: "Rate",
    slow: "Slow",
    fast: "Fast",
    pitch: "Pitch",
    neutral: "Neutral",
    intense: "Intense",
    contSpeech: "Continuous speech",
    autoSpeech: "Auto speech",
    unsupportRecTip:
      "Voice recognition is not supported in the current environment. Please refer to the documentation.",
    loadRecVoice: "Load language",
    lang: "Language",
    dialect: "Dialect",
    autoSendKey: "Auto send keyword",
    autoStopKey: "Auto stop keyword",
    autoSendDelay: "Auto send delay time",
    second: "s",
    keepListenMic: "Keep listen",
    send: "Send",
    askTip: "Type message here",
    clearChat: "Clear chat",
    general: "General",
    hotkey: "Hotkey",
    data: "Data",
    theme: "Theme",
    darkTheme: "Dark",
    lightTheme: "Light",
    autoWord: "Auto",
    systemTheme: "System",
    customDarkTheme: "Custom dark theme",
    startDark: "Start",
    endDark: "End",
    aiEndpoint: " endpoint",
    aiKey: " API key",
    aiModel: "Custom model name",
    used: "Used ",
    available: "Avail ",
    navKey: "Toggle nav",
    fullKey: "Window size",
    themeKey: "Toggle theme",
    langKey: "Toggle lang",
    inputKey: "Message",
    voiceKey: "Voice",
    resetTip: "Restore default",
    recKey: "Recognition",
    speechKey: "Start speech",
    export: "Export",
    import: "Import",
    clear: "Clear",
    reset: "Reset",
    localStore: "Local storage",
    forceReTip: "Force refresh page?",
    noSpeechTip:
      "No speech was detected. You may need to adjust your microphone settings.",
    noMicTip:
      "No microphone was found. Ensure that a microphone is installed and microphone settings are configured correctly.",
    noMicPerTip: "Permission to use microphone is blocked.",
    azureInvalidTip:
      "Invalid access key or wrong Azure region endpoint, please check!",
    errorAiKeyTip: "Invalid or incorrect API key, please check API key!",
    copyCode: "Copy code",
    copySuccess: "Success",
    update: "Update",
    cancel: "Cancel",
    delMsgTip: "Delete this message?",
    edit: "Edit",
    refresh: "Refresh",
    continue: "Continue",
    copy: "Copy",
    del: "Delete",
    downAudio: "Download audio",
    speech: "Speech",
    chats: " chats",
    delFolderTip: "Delete this folder?",
    delChatTip: "Delete this chat?",
    exportSuccTip: "Export successful!",
    importSuccTip: "Import successful!",
    importFailTip: "Import failed, please check the file format!",
    clearChatSuccTip: "Clear chats data successful!",
    resetSetSuccTip: "Reset settings successful!",
    clearAllTip: "Delete all chats and folders?",
    resetSetTip: "Restore all settings to default?",
    hotkeyConflict: "Hotkey conflict, please choose another key!",
    customDarkTip: "Start time and end time cannot be the same!",
    timeoutTip: "Request timeout, please try again later!",
    largeReqTip:
      "Request is too large, please delete part of the chat or cancel continuous chat!",
    noModelPerTip:
      "Not permission to use this model, please choose another GPT model!",
    apiRateTip: "Trigger API call rate limit, please try again later!",
    exceedLimitTip: "API usage exceeded limit, please check your bill!",
    badGateTip: "Gateway error or timeout, please try again later!",
    badEndpointTip: "Failed to access the endpoint, please check the endpoint!",
    clearChatTip: "Clear this chat?",
    cantSpeechTip:
      "Current voice cannot synthesize this message, please choose another voice or message!",
    cantTranscribeTip: "Voice recognition failed, please try again!",
    noStreamTip:
      "Automatic features aren't available for non-realtime speech recognition service!",
    localQuotaExceedTip:
      "Local storage exceeded limit, please export chats data and clear or delete some chats!",
  },
  zh: {
    description: "简洁而强大的ChatGPT应用",
    newChat: "新建会话",
    newChatName: "新的会话",
    newFolder: "新建文件夹",
    newFolderName: "新文件夹",
    search: "搜索",
    matchCaseTip: "区分大小写",
    forceRe: "强制刷新",
    clearAll: "清空全部",
    setting: "设置",
    nav: "导航",
    winedWin: "窗口",
    fullWin: "全屏",
    quickSet: "快速设置",
    chat: "会话",
    tts: "语音合成",
    stt: "语音识别",
    avatar: "用户头像",
    systemRole: "系统角色",
    presetRole: "预设角色",
    default: "默认",
    assistant: "助手",
    cat: "猫娘",
    emoji: "表情",
    withImg: "有图",
    defaultText: "",
    assistantText: "你是一个乐于助人的助手，尽量简明扼要地回答",
    catText: "你是一个可爱的猫娘，每句话结尾都要带个'喵'",
    emojiText: "你的性格很活泼，每句话中都要有至少一个emoji图标",
    imageText:
      "当你需要发送图片的时候，请用 markdown 语言生成，不要反斜线，不要代码框，需要使用 unsplash API时，遵循一下格式， https://source.unsplash.com/960x640/? ＜英文关键词＞",
    nature: "角色性格",
    natureNeg: "准确严谨",
    naturePos: "灵活创新",
    quality: "回答质量",
    qualityNeg: "重复保守",
    qualityPos: "胡言乱语",
    chatsWidth: "会话宽度",
    typeSpeed: "打字机速度",
    continuousLen: "上下文消息数",
    msgAbbr: "条",
    slow: "慢",
    fast: "快",
    longReply: "长回复",
    ttsService: "语音合成服务",
    sttService: "语音识别服务",
    openaiTTS: "OpenAI语音",
    azureTTS: "Azure语音",
    edgeTTS: "Edge语音",
    systemTTS: "系统语音",
    azureRegion: "Azure区域",
    azureKey: "Azure密钥",
    loadVoice: "加载语音",
    voiceName: "切换",
    userVoice: "用户语音",
    replyVoice: "回答语音",
    TTSTest: "你好，很高兴认识你。",
    play: "播放",
    pause: "暂停",
    resume: "恢复",
    stop: "停止",
    style: "风格",
    role: "角色",
    volume: "音量",
    low: "低",
    high: "高",
    rate: "语速",
    slow: "慢",
    fast: "快",
    pitch: "音调",
    neutral: "平淡",
    intense: "起伏",
    contSpeech: "连续朗读",
    autoSpeech: "自动朗读",
    unsupportRecTip: "当前环境不支持语音识别，请查阅文档。",
    loadRecVoice: "加载语言",
    lang: "语言",
    dialect: "方言",
    autoSendKey: "自动发送关键词",
    autoStopKey: "自动停止关键词",
    autoSendDelay: "自动发送延迟时间",
    second: "秒",
    keepListenMic: "保持监听",
    send: "发送",
    askTip: "来问点什么吧",
    clearChat: "清空会话",
    general: "通用",
    hotkey: "快捷键",
    data: "数据",
    theme: "主题",
    darkTheme: "深色",
    lightTheme: "浅色",
    autoWord: "自动",
    systemTheme: "跟随系统",
    customDarkTheme: "自定义深色主题时间",
    startDark: "开始时间",
    endDark: "结束时间",
    aiEndpoint: "接口",
    aiKey: "API密钥",
    aiModel: "自定义模型",
    used: "已用 ",
    available: "可用 ",
    navKey: "切换导航",
    fullKey: "全屏/窗口",
    themeKey: "切换主题",
    langKey: "切换语言",
    inputKey: "输入框",
    voiceKey: "语音",
    resetTip: "重置设置",
    recKey: "语音输入",
    speechKey: "朗读会话",
    export: "导出",
    import: "导入",
    clear: "清空",
    reset: "重置",
    localStore: "本地存储",
    forceReTip: "是否强制刷新页面？",
    noSpeechTip: "未识别到语音，请调整麦克风后重试！",
    noMicTip: "未识别到麦克风，请确保已安装麦克风！",
    noMicPerTip: "未允许麦克风权限！",
    azureInvalidTip: "Azure区域错误或密钥无效，请检查！",
    errorAiKeyTip: "API密钥错误或失效，请检查API密钥！",
    copyCode: "复制代码",
    copySuccess: "复制成功",
    update: "更新",
    cancel: "取消",
    delMsgTip: "是否删除此消息？",
    edit: "编辑",
    refresh: "刷新",
    continue: "继续",
    copy: "复制",
    del: "删除",
    downAudio: "下载语音",
    speech: "朗读",
    chats: "个会话",
    delFolderTip: "是否删除此文件夹？",
    delChatTip: "是否删除此会话？",
    exportSuccTip: "导出成功！",
    importSuccTip: "导入成功！",
    importFailTip: "导入失败，请检查文件格式！",
    clearChatSuccTip: "清空会话成功！",
    resetSetSuccTip: "重置设置成功！",
    clearAllTip: "是否删除所有会话和文件夹？",
    resetSetTip: "是否还原所有设置为默认值？",
    hotkeyConflict: "快捷键冲突，请选择其他键位！",
    customDarkTip: "开始时间和结束时间不能相同！",
    timeoutTip: "请求超时，请稍后重试！",
    largeReqTip: "请求内容过大，请删除部分对话或关闭连续对话！",
    noModelPerTip: "无权使用此模型，请选择其他GPT模型！",
    apiRateTip: "触发API调用频率限制，请稍后重试！",
    exceedLimitTip: "API使用超出限额，请检查您的账单！",
    badGateTip: "网关错误或超时，请稍后重试！",
    badEndpointTip: "访问接口失败，请检查接口！",
    clearChatTip: "是否清空此会话？",
    cantSpeechTip: "当前语音无法合成此消息，请选择其他语音或消息！",
    cantTranscribeTip: "语音识别失败，请重试！",
    noStreamTip: "非实时语音识别服务无法使用自动功能！",
    localQuotaExceedTip: "本地存储超出限额，请导出会话并清空或删除部分会话！",
  },
};
const translateElement = (ele, type) => {
  const key = ele.getAttribute("data-i18n-" + type);
  const translation = translations[locale][key];
  if (type === "title") {
    ele.setAttribute("title", translation);
  } else if (type === "place") {
    ele.setAttribute("placeholder", translation);
  } else if (type === "value") {
    ele.setAttribute("value", translation);
  } else {
    ele.textContent = translation;
  }
};
const initLocale = () => {
  document.querySelectorAll("[data-i18n-title]").forEach((ele) => {
    translateElement(ele, "title");
  });
  document.querySelectorAll("[data-i18n-place]").forEach((ele) => {
    translateElement(ele, "place");
  });
  document.querySelectorAll("[data-i18n-value]").forEach((ele) => {
    translateElement(ele, "value");
  });
  document.querySelectorAll("[data-i18n-key]").forEach((ele) => {
    translateElement(ele, "key");
  });
  document.querySelectorAll("[data-i18n-theme]").forEach((ele) => {
    let key =
      themeMode === 2
        ? "autoWord"
        : themeMode === 1
        ? "lightTheme"
        : "darkTheme";
    ele.setAttribute("title", translations[locale][key]);
  });
  document.querySelectorAll("[data-i18n-window]").forEach((ele) => {
    let key = isFull ? "winedWin" : "fullWin";
    ele.setAttribute("title", translations[locale][key]);
  });
  document.head.children[3].setAttribute(
    "content",
    translations[locale]["description"]
  );
};
initLocale();
const changeLocale = () => {
  initLocale();
  document
    .querySelectorAll("[data-type='chatEdit'],[data-type='folderEdit']")
    .forEach((ele) => {
      ele.children[0].textContent = translations[locale]["edit"];
    });
  document
    .querySelectorAll("[data-type='chatDel'],[data-type='folderDel']")
    .forEach((ele) => {
      ele.children[0].textContent = translations[locale]["del"];
    });
  document.querySelectorAll("[data-type='folderAddChat']").forEach((ele) => {
    ele.children[0].textContent = translations[locale]["newChat"];
  });
  document.querySelectorAll("[data-id]").forEach((ele) => {
    let key = ele.getAttribute("data-id");
    if (key.endsWith("Md")) {
      if (key === "speechMd" || key === "pauseMd" || key === "resumeMd") {
        ele.children[0].textContent = translations[locale][key.slice(0, -2)];
      } else if (key === "refreshMd") {
        ele.setAttribute(
          "title",
          translations[locale][
            ele.classList.contains("refreshReq") ? "refresh" : "continue"
          ]
        );
      } else {
        ele.setAttribute("title", translations[locale][key.slice(0, -2)]);
      }
    }
  });
  document.querySelectorAll(".folderNum").forEach((ele) => {
    let num = ele.textContent.match(/\d+/)[0];
    ele.textContent = num + translations[locale]["chats"];
  });
  document.querySelectorAll(".u-mdic-copy-btn").forEach((ele) => {
    ele.setAttribute("text", translations[locale]["copyCode"]);
  });
  document.querySelectorAll(".u-mdic-copy-notify").forEach((ele) => {
    ele.setAttribute("text", translations[locale]["copySuccess"]);
  });
  if (editingIdx !== void 0) {
    document.querySelector("[data-i18n-key='send']").textContent =
      translations[locale]["update"];
    document
      .querySelector("[data-i18n-title='clearChat']")
      .setAttribute("title", translations[locale]["cancel"]);
  }
  loadPrompt();
};

const windowEle = document.getElementsByClassName("chat_window")[0];
const messagesEle = document.getElementsByClassName("messages")[0];
const chatlog = document.getElementById("chatlog");
const stopEle = document.getElementById("stopChat");
const sendBtnEle = document.getElementById("sendbutton");
const clearEle = document.getElementsByClassName("clearConv")[0];
const inputAreaEle = document.getElementById("chatinput");
const settingEle = document.getElementById("setting");
const dialogEle = document.getElementById("setDialog");
const selectorEle = document.getElementById("selector");
const modelSetEle = document.getElementById("modelDialog");
const lightEle = document.getElementById("toggleLight");
const setLightEle = document.getElementById("setLight");
const autoThemeEle = document.getElementById("autoDetail");
const systemEle = document.getElementById("systemInput");
const speechServiceEle = document.getElementById("preSetService");
const recServiceEle = document.getElementById("preRecService");
const newChatEle = document.getElementById("newChat");
const folderListEle = document.getElementById("folderList");
const chatListEle = document.getElementById("chatList");
const searchChatEle = document.getElementById("searchChat");
const voiceRecEle = document.getElementById("voiceRecIcon");
const voiceRecSetEle = document.getElementById("voiceRecSetting");
const preEle = document.getElementById("preSetSystem");
let voicesData; // 语音数据
let voiceType = 1; // 设置 0: 提问语音，1：回答语音
let voiceRole = []; // 语音
let voiceTestText; // 测试语音文本
let voiceVolume = []; //音量
let voiceRate = []; // 语速
let voicePitch = []; // 音调
let enableContVoice; // 连续朗读
let enableAutoVoice; // 自动朗读
let existVoice = 2; // 4:OpenAI语音 3:Azure语音 2:edge在线语音, 1:本地语音, 0:不支持语音
const azureRegions = [
  "southafricanorth",
  "eastasia",
  "southeastasia",
  "australiaeast",
  "centralindia",
  "japaneast",
  "japanwest",
  "koreacentral",
  "canadacentral",
  "northeurope",
  "westeurope",
  "francecentral",
  "germanywestcentral",
  "norwayeast",
  "swedencentral",
  "switzerlandnorth",
  "switzerlandwest",
  "uksouth",
  "uaenorth",
  "brazilsouth",
  "qatarcentral",
  "centralus",
  "eastus",
  "eastus2",
  "northcentralus",
  "southcentralus",
  "westcentralus",
  "westus",
  "westus2",
  "westus3",
];
let azureRegion;
let azureKey;
let azureRole = [];
let azureStyle = [];
const supportSpe = !!(
  window.speechSynthesis && window.SpeechSynthesisUtterance
);
const isSafeEnv = window.isSecureContext; // 安全上下文
const supportLocalRec = isSafeEnv && !!window.webkitSpeechRecognition; // 是否支持本地语音识别输入
const supportOnlineRec =
  isSafeEnv &&
  navigator.mediaDevices &&
  navigator.mediaDevices.getUserMedia &&
  window.AudioContext &&
  ("audioWorklet" in window.AudioContext.prototype ||
    "createScriptProcessor" in window.AudioContext.prototype);
const supportOnlineLegacyRec =
  isSafeEnv &&
  navigator.mediaDevices &&
  navigator.mediaDevices.getUserMedia &&
  !!window.MediaRecorder &&
  (MediaRecorder.isTypeSupported("audio/webm") ||
    MediaRecorder.isTypeSupported("audio/mp4"));
const supportRec =
  supportLocalRec || supportOnlineRec || supportOnlineLegacyRec;
let existRec = 1; // 2:Azure语音，1:系统语音
let azureRecRegion;
let azureRecKey;
let recing = false;
let autoSendWord; // 自动发送关键词
let autoStopWord; // 自动停止关键词
let autoSendTime; // 自动发送延迟时间
let keepListenMic; // 保持监听麦克风
let autoSendTimer;
let resetRecRes;
let toggleRecEv;
const isAndroid = /\bAndroid\b/i.test(navigator.userAgent);
const isApple = /(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent);
const isSafari =
  /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
const isPWA =
  navigator.standalone ||
  window.matchMedia("(display-mode: standalone)").matches;
if (isPWA) {
  let bottomEle = document.querySelector(".bottom_wrapper");
  let footerEle = document.querySelector(".navFooter");
  footerEle.style.marginBottom = bottomEle.style.marginBottom = "8px";
}
const dayMs = 8.64e7;
refreshPage.onclick = () => {
  if (confirmAction(translations[locale]["forceReTip"])) {
    location.href =
      location.origin + location.pathname + "?" + new Date().getTime();
  }
};
const noLoading = () => {
  return (
    !loading && (!currentResEle || currentResEle.dataset.loading !== "true")
  );
};
const uuidv4 = (upper) => {
  let uuid = ([1e7] + 1e3 + 4e3 + 8e3 + 1e11).replace(/[018]/g, (c) =>
    (
      c ^
      (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
    ).toString(16)
  );
  return upper ? uuid.toUpperCase() : uuid;
};
if (!isMobile) inputAreaEle.focus();
const textInputEvent = () => {
  if (noLoading())
    sendBtnEle.classList.toggle(
      "activeSendBtn",
      inputAreaEle.value.trim().length
    );
  inputAreaEle.style.height = "47px";
  inputAreaEle.style.height = inputAreaEle.scrollHeight + "px";
};
inputAreaEle.oninput = textInputEvent;
const toggleNavEv = () => {
  let isShowNav = document.body.classList.toggle("show-nav");
  if (window.innerWidth > 800) {
    localStorage.setItem("pinNav", isShowNav);
  }
};
document.body.addEventListener("mousedown", (event) => {
  if (event.target.className === "toggler") {
    toggleNavEv();
  } else if (event.target.className === "overlay") {
    document.body.classList.remove("show-nav");
  } else if (event.target === document.body) {
    if (window.innerWidth <= 800) {
      document.body.classList.remove("show-nav");
    }
  }
});
const endSetEvent = (ev) => {
  if (!document.getElementById("sysDialog").contains(ev.target)) {
    ev.preventDefault();
    ev.stopPropagation();
    endSet();
  }
};
const endSet = () => {
  document.getElementById("sysMask").style.display = "none";
  document.body.removeEventListener("click", endSetEvent, true);
};
document.getElementById("closeSet").onclick = endSet;
document.getElementById("sysSetting").onclick = () => {
  document.getElementById("sysMask").style.display = "flex";
  checkStorage();
  document.getElementById("sysMask").onmousedown = endSetEvent;
};
const setAutoTimer = () => {
  if (autoSendTime) {
    autoSendTimer = setTimeout(() => {
      genFunc();
      autoSendTimer = void 0;
    }, autoSendTime * 1000);
  }
};
const clearAutoSendTimer = () => {
  if (autoSendTimer !== void 0) {
    clearTimeout(autoSendTimer);
    autoSendTimer = void 0;
  }
};
if (!supportLocalRec) recServiceEle.remove(2);
if (!supportOnlineRec) recServiceEle.remove(1);
if (!supportOnlineLegacyRec) recServiceEle.remove(0);
const initRecVal = () => {
  if (!supportRec) {
    noRecTip.style.display = "block";
    noRecTip.parentElement.firstElementChild.style.display = "none";
    noRecTip.parentElement.children[1].style.display = "none";
    return;
  }
  let localRecType = localStorage.getItem("existRec");
  recServiceEle.value = existRec = parseInt(
    localRecType || (supportLocalRec ? "1" : "2")
  );
};
initRecVal();
const clearAzureRec = () => {
  azureRecKey = void 0;
  localStorage.removeItem(azureRecRegion + "RecData");
  azureRecData = void 0;
  azureRecRegion = void 0;
  azureRecKeyInput.parentElement.style.display = "none";
  preRecAzureRegion.parentElement.style.display = "none";
};
const featStreamRec = (hide) => {
  document
    .querySelectorAll('[data-feat="forStream"]')
    .forEach((item) => (item.style.display = hide ? "none" : "block"));
  document
    .querySelectorAll('[data-feat="forNoStream"]')
    .forEach((item) => (item.style.display = hide ? "block" : "none"));
};
let azureRecData, systemRecData, checkAzureRecAbort;
const toggleRecCheck = (bool) => {
  checkRecLoad.style.display = bool ? "flex" : "none";
  recDetail.style.display = bool ? "none" : "block";
  hotKeyVoiceRec.parentElement.style.display = bool ? "none" : "block";
  document.getElementById("voiceRec").style.display = bool ? "none" : "block";
  if (bool) inputAreaEle.classList.remove("message_if_voice");
  else inputAreaEle.classList.add("message_if_voice");
};
recServiceEle.onchange = () => {
  if (!supportRec) return;
  existRec = parseInt(recServiceEle.value);
  localStorage.setItem("existRec", existRec);
  toggleRecCheck(true);
  if (checkAzureRecAbort && !checkAzureRecAbort.signal.aborted) {
    checkAzureRecAbort.abort();
    checkAzureRecAbort = void 0;
  }
  if (existRec === 3) {
    clearAzureRec();
    loadOpenAIRec();
    featStreamRec(true);
  } else if (existRec === 2) {
    azureRecKeyInput.parentElement.style.display = "block";
    preRecAzureRegion.parentElement.style.display = "block";
    loadAzureRec();
    featStreamRec();
  } else {
    clearAzureRec();
    loadLocalRec();
    featStreamRec();
  }
};
const loadLocalRec = () => {
  initRecSetting();
};
const loadOpenAIRec = () => {
  initRecSetting();
};
const loadAzureRec = () => {
  let checking = false;
  const checkAzureFunc = () => {
    if (checking) return;
    if (azureRecKey) {
      checking = true;
      checkRecLoad.classList.add("voiceChecking");
      checkAzureRecAbort = new AbortController();
      setTimeout(() => {
        if (checkAzureRecAbort && !checkAzureRecAbort.signal.aborted) {
          checkAzureRecAbort.abort();
          checkAzureRecAbort = void 0;
        }
      }, 15000);
      getAzureToken(checkAzureRecAbort.signal)
        .then(() => {
          getRecList(checkAzureRecAbort.signal)
            .then(() => {
              initRecSetting(azureRecData);
            })
            .catch((e) => {})
            .finally(() => {
              checkRecLoad.classList.remove("voiceChecking");
              checking = false;
            });
        })
        .catch((e) => {})
        .finally(() => {
          checkRecLoad.classList.remove("voiceChecking");
          checking = false;
        });
    }
  };
  checkRecLoad.onclick = checkAzureFunc;
  const getAzureToken = (signal) => {
    return new Promise((res, rej) => {
      fetch(
        "https://" +
          azureRecRegion +
          ".api.cognitive.microsoft.com/sts/v1.0/issueToken",
        {
          signal,
          method: "POST",
          headers: {
            "Ocp-Apim-Subscription-Key": azureRecKey,
          },
        }
      )
        .then((response) => {
          response.text().then((text) => {
            try {
              let json = JSON.parse(text);
              notyf.error(translations[locale]["azureInvalidTip"]);
              rej();
            } catch (e) {
              res();
            }
          });
        })
        .catch((e) => {
          localStorage.removeItem(azureRecRegion + "RecData");
          azureRecData = void 0;
          rej();
        });
    });
  };
  const getRecList = (signal) => {
    return new Promise((res, rej) => {
      if (azureRecData) res();
      else {
        let localAzureRecData = localStorage.getItem(
          azureRecRegion + "RecData"
        );
        if (localAzureRecData) {
          azureRecData = JSON.parse(localAzureRecData);
          res();
        } else {
          fetch(
            "https://" +
              azureRecRegion +
              ".stt.speech.microsoft.com/api/v1.0/languages/recognition",
            {
              signal,
            }
          )
            .then((response) => {
              response
                .json()
                .then((json) => {
                  azureRecData = json;
                  localStorage.setItem(
                    azureRecRegion + "RecData",
                    JSON.stringify(json)
                  );
                  res();
                })
                .catch((e) => {
                  notyf.error(translations[locale]["azureInvalidTip"]);
                  rej();
                });
            })
            .catch((e) => {
              localStorage.removeItem(azureRecRegion + "RecData");
              azureRecData = void 0;
              rej();
            });
        }
      }
    });
  };
  let azureRecRegionEle = document.getElementById("preRecAzureRegion");
  if (!azureRecRegionEle.options.length) {
    azureRegions.forEach((region, i) => {
      let option = document.createElement("option");
      option.value = region;
      option.text = region;
      azureRecRegionEle.options.add(option);
    });
  }
  let localAzureRecRegion = localStorage.getItem("azureRecRegion");
  if (localAzureRecRegion) {
    azureRecRegion = localAzureRecRegion;
    azureRecRegionEle.value = localAzureRecRegion;
  }
  azureRecRegionEle.onchange = () => {
    azureRecRegion = azureRecRegionEle.value;
    localStorage.setItem("azureRecRegion", azureRecRegion);
    toggleRecCheck(true);
  };
  azureRecRegionEle.dispatchEvent(new Event("change"));
  let azureRecKeyEle = document.getElementById("azureRecKeyInput");
  let localAzureRecKey = localStorage.getItem("azureRecKey");
  if (localAzureRecKey) {
    azureRecKey = localAzureRecKey;
    azureRecKeyEle.value = localAzureRecKey;
  }
  azureRecKeyEle.onchange = () => {
    azureRecKey = azureRecKeyEle.value;
    localStorage.setItem("azureRecKey", azureRecKey);
    toggleRecCheck(true);
  };
  azureRecKeyEle.dispatchEvent(new Event("change"));
  if (azureRecKey) checkAzureFunc();
};
const azureLangTrans = {
  "en-au": "Australia",
  "en-ca": "Canada",
  "en-gb": "United Kingdom",
  "en-gh": "Ghana",
  "en-hk": "Hong Kong SAR",
  "en-ie": "Ireland",
  "en-in": "India",
  "en-ke": "Kenya",
  "en-ng": "Nigeria",
  "en-nz": "New Zealand",
  "en-ph": "Philippines",
  "en-sg": "Singapore",
  "en-tz": "Tanzania",
  "en-us": "United States",
  "en-za": "South Africa",
  "nan-cn": "闽南语，简体",
  "wuu-cn": "吴语，简体",
  "yue-cn": "粤语，简体",
  "zh-cn": "普通话，简体",
  "zh-cn-anhui": "安徽江淮普通话，简体",
  "zh-cn-bilingual": "普通话，英语双语",
  "zh-cn-gansu": "甘肃兰银普通话，简体",
  "zh-cn-guangxi": "广西口音普通话，简体",
  "zh-cn-henan": "中原官话河南，简体",
  "zh-cn-hunan": "湖南口音普通话，简体",
  "zh-cn-liaoning": "东北官话，简体",
  "zh-cn-shaanxi": "中原官话陕西，简体",
  "zh-cn-shandong": "冀鲁官话，简体",
  "zh-cn-shanxi": "山西口音普通话，简体",
  "zh-cn-sichuan": "西南官话，简体",
  "zh-hk": "粤语，繁体",
  "zh-sg": "简体，新加坡",
  "zh-tw": "台湾普通话",
};
const initRecSetting = (azureData) => {
  let langs = [["中文"], ["English"]];
  if (azureData) {
    azureData.forEach((item) => {
      if (item.startsWith("en-")) {
        let lowCase = item.toLowerCase();
        let dialectName = azureLangTrans[lowCase] || lowCase;
        if (lowCase == "en-us") langs[1].splice(1, 0, [lowCase, dialectName]);
        else langs[1].push([lowCase, dialectName]);
      } else if (item.indexOf("CN") != -1 || item.indexOf("zh") != -1) {
        let lowCase = item.toLowerCase();
        let dialectName = azureLangTrans[lowCase] || lowCase;
        if (lowCase == "zh-cn") langs[0].splice(1, 0, [lowCase, dialectName]);
        else langs[0].push([lowCase, dialectName]);
      }
    });
  } else if (existRec === 3) {
    langs = [
      ["自动检测", ["", "自动检测"]],
      ["中文", ["zh", "汉语"]],
      ["English", ["en", "English"]],
    ];
  } else {
    langs = [
      // from https://www.google.com/intl/en/chrome/demos/speech.html
      [
        "中文",
        ["cmn-Hans-CN", "普通话 (大陆)"],
        ["cmn-Hans-HK", "普通话 (香港)"],
        ["cmn-Hant-TW", "中文 (台灣)"],
        ["yue-Hant-HK", "粵語 (香港)"],
      ],
      [
        "English",
        ["en-US", "United States"],
        ["en-GB", "United Kingdom"],
        ["en-AU", "Australia"],
        ["en-CA", "Canada"],
        ["en-IN", "India"],
        ["en-KE", "Kenya"],
        ["en-TZ", "Tanzania"],
        ["en-GH", "Ghana"],
        ["en-NZ", "New Zealand"],
        ["en-NG", "Nigeria"],
        ["en-ZA", "South Africa"],
        ["en-PH", "Philippines"],
      ],
    ];
  }
  toggleRecCheck(false);
  if (locale !== "zh") {
    if (existRec === 3) {
      langs[0][0] = langs[0][1][1] = translations[locale]["autoWord"];
      let idx = langs.findIndex((item) => {
        return item[1][0] === locale;
      });
      let [temp] = langs.splice(idx, 1);
      langs.splice(1, 0, temp);
    } else langs = langs.reverse();
  }
  selectLangOption.options.length = select_language.options.length = 0;
  langs.forEach((lang, i) => {
    select_language.options.add(new Option(lang[0], i));
    selectLangOption.options.add(new Option(lang[0], i));
  });
  const updateCountry = function () {
    selectLangOption.selectedIndex = select_language.selectedIndex =
      this.selectedIndex;
    select_dialect.innerHTML = "";
    selectDiaOption.innerHTML = "";
    let list = langs[select_language.selectedIndex];
    for (let i = 1; i < list.length; i++) {
      select_dialect.options.add(new Option(list[i][1], list[i][0]));
      selectDiaOption.options.add(new Option(list[i][1], list[i][0]));
    }
    select_dialect.style.visibility =
      list[1].length == 1 ? "hidden" : "visible";
    selectDiaOption.parentElement.style.visibility =
      list[1].length == 1 ? "hidden" : "visible";
    localStorage.setItem("voiceRecLang", select_dialect.value);
  };
  let localLangIdx = 0;
  let localDiaIdx = 0;
  let localRecLang = localStorage.getItem("voiceRecLang") || langs[0][1][0];
  if (localRecLang) {
    let localIndex = langs.findIndex((item) => {
      let diaIdx = item.findIndex((lang) => {
        return lang instanceof Array && lang[0] === localRecLang;
      });
      if (diaIdx !== -1) {
        localDiaIdx = diaIdx - 1;
        return true;
      }
      return false;
    });
    if (localIndex !== -1) localLangIdx = localIndex;
  }
  selectLangOption.onchange = updateCountry;
  select_language.onchange = updateCountry;
  selectDiaOption.onchange = select_dialect.onchange = function () {
    selectDiaOption.selectedIndex = select_dialect.selectedIndex =
      this.selectedIndex;
    localStorage.setItem("voiceRecLang", select_dialect.value);
  };
  selectLangOption.selectedIndex = select_language.selectedIndex = localLangIdx;
  select_language.dispatchEvent(new Event("change"));
  selectDiaOption.selectedIndex = select_dialect.selectedIndex = localDiaIdx;
  select_dialect.dispatchEvent(new Event("change"));
  initRecEvent();
};
let recSetTimer;
let initRecFunc = () => {
  if (!supportRec) return;
  let localAutoSendWord = localStorage.getItem("autoVoiceSendWord");
  autoSendWord = autoSendText.value =
    localAutoSendWord || autoSendText.getAttribute("value") || "";
  autoSendText.onchange = () => {
    autoSendWord = autoSendText.value;
    localStorage.setItem("autoVoiceSendWord", autoSendWord);
  };
  autoSendText.dispatchEvent(new Event("change"));
  let localAutoStopWord = localStorage.getItem("autoVoiceStopWord");
  autoStopWord = autoStopText.value =
    localAutoStopWord || autoStopText.getAttribute("value") || "";
  autoStopText.onchange = () => {
    autoStopWord = autoStopText.value;
    localStorage.setItem("autoVoiceStopWord", autoStopWord);
  };
  autoStopText.dispatchEvent(new Event("change"));
  let outEle = document.getElementById("autoSendTimeout");
  let localTimeout = localStorage.getItem("autoVoiceSendOut");
  outEle.value = autoSendTime = parseInt(
    localTimeout || outEle.getAttribute("value")
  );
  outEle.oninput = () => {
    outEle.style.backgroundSize =
      ((outEle.value - outEle.min) * 100) / (outEle.max - outEle.min) +
      "% 100%";
    autoSendTime = parseInt(outEle.value);
    localStorage.setItem("autoVoiceSendOut", outEle.value);
  };
  outEle.dispatchEvent(new Event("input"));
  outEle.onchange = () => {
    let hasAutoTimer = !!autoSendTimer;
    clearAutoSendTimer();
    if (hasAutoTimer) setAutoTimer();
  };
  const keepMicEle = document.getElementById("keepListenMic");
  let localKeepMic = localStorage.getItem("keepListenMic");
  keepMicEle.checked = keepListenMic =
    (localKeepMic || keepMicEle.getAttribute("checked")) === "true";
  keepMicEle.onchange = () => {
    keepListenMic = keepMicEle.checked;
    localStorage.setItem("keepListenMic", keepListenMic);
  };
  keepMicEle.dispatchEvent(new Event("change"));
  const closeEvent = (ev) => {
    if (voiceRecSetEle.contains(ev.target)) return;
    if (!voiceRecSetEle.contains(ev.target)) {
      voiceRecSetEle.style.display = "none";
      document.removeEventListener("mousedown", closeEvent, true);
      voiceRecEle.classList.remove("voiceLong");
    }
  };
  const longEvent = () => {
    voiceRecSetEle.style.display = "block";
    document.addEventListener("mousedown", closeEvent, true);
  };
  const voiceDownEvent = (ev) => {
    ev.preventDefault();
    let i = 0;
    voiceRecEle.classList.add("voiceLong");
    recSetTimer = setInterval(() => {
      i += 1;
      if (i >= 3) {
        clearInterval(recSetTimer);
        recSetTimer = void 0;
        longEvent();
      }
    }, 100);
  };
  const voiceUpEvent = (ev) => {
    ev.preventDefault();
    if (recSetTimer !== void 0) {
      toggleRecEv();
      clearInterval(recSetTimer);
      recSetTimer = void 0;
      voiceRecEle.classList.remove("voiceLong");
    }
  };
  voiceRecEle.onmouseup = voiceUpEvent;
  voiceRecEle.ontouchend = voiceUpEvent;
  voiceRecEle.onmousedown = voiceDownEvent;
  voiceRecEle.ontouchstart = voiceDownEvent;
};
initRecFunc();
class RiffPcmEncoder {
  constructor(actualSampleRate, desiredSampleRate) {
    this.privActualSampleRate = actualSampleRate;
    this.privDesiredSampleRate = desiredSampleRate;
  }
  encode(actualAudioFrame) {
    const audioFrame = this.downSampleAudioFrame(
      actualAudioFrame,
      this.privActualSampleRate,
      this.privDesiredSampleRate
    );
    if (!audioFrame) return null;
    const audioLength = audioFrame.length * 2;
    const buffer = new ArrayBuffer(audioLength);
    const view = new DataView(buffer);
    this.floatTo16BitPCM(view, 0, audioFrame);
    return buffer;
  }
  floatTo16BitPCM(view, offset, input) {
    for (let i = 0; i < input.length; i++, offset += 2) {
      const s = Math.max(-1, Math.min(1, input[i]));
      view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
    }
  }
  downSampleAudioFrame(srcFrame, srcRate, dstRate) {
    if (!srcFrame) return null;
    if (dstRate === srcRate || dstRate > srcRate) return srcFrame;
    const ratio = srcRate / dstRate;
    const dstLength = Math.round(srcFrame.length / ratio);
    const dstFrame = new Float32Array(dstLength);
    let srcOffset = 0;
    let dstOffset = 0;
    while (dstOffset < dstLength) {
      const nextSrcOffset = Math.round((dstOffset + 1) * ratio);
      let accum = 0;
      let count = 0;
      while (srcOffset < nextSrcOffset && srcOffset < srcFrame.length) {
        accum += srcFrame[srcOffset++];
        count++;
      }
      dstFrame[dstOffset++] = accum / count;
    }
    return dstFrame;
  }
}
let recSocket;
class Recorder {
  constructor() {
    this.ready = false;
    this.connId = "";
    this.reqId = "";
    this.label = "";
    this.processScriptURL = "";
    this.forceStop = false;
    this.sampleRate = 16000;
    // avgBytesPerSec / 10, 0.1s
    this.bufferSize = this.sampleRate / 5;
    this.chunks = [];
    this.chunksByte = 0;
    // "Content-Type: audio/x-wav\r\n" + WAV header
    this.wavHeader = new Uint8Array([
      67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 32, 97, 117,
      100, 105, 111, 47, 120, 45, 119, 97, 118, 13, 10, 82, 73, 70, 70, 0, 0, 0,
      0, 87, 65, 86, 69, 102, 109, 116, 32, 16, 0, 0, 0, 1, 0, 1, 0, 128, 62, 0,
      0, 0, 125, 0, 0, 2, 0, 16, 0, 100, 97, 116, 97, 0, 0, 0, 0,
    ]);
  }
  initRecorder() {
    return new Promise((res) => {
      // microsoft cognitive-services-speech-sdk-js
      this.context = navigator.mediaDevices.getSupportedConstraints().sampleRate
        ? new AudioContext({ sampleRate: this.sampleRate })
        : new AudioContext();
      this.audioInput = this.context.createMediaStreamSource(this.stream);
      if (this.context.audioWorklet) {
        if (this.processScriptURL == "") {
          const workletScript = `${RiffPcmEncoder.toString()}
                            class SP extends AudioWorkletProcessor {
                            constructor(options) {
                                super(options);
                                this.sampleRate = ${this.sampleRate};
                                // avgBytesPerSec / 10, 0.1s
                                this.bufferSize = ${this.bufferSize};
                                this.encoder = new RiffPcmEncoder(options.processorOptions.sampleRate, this.sampleRate);
                                this.chunks = [];
                                this.chunksByte = 0;
                                this.processing = true;
                                this.port.onmessage = (e) => {
                                if (e.data === "stop") {
                                    this.processing = false;
                                    this.port.close();
                                }
                                }
                            }
                            concat() {
                                let result = new Uint8Array(this.bufferSize);
                                let offset = 0;
                                for (let i = 0; i < this.chunks.length; i++) {
                                result.set(this.chunks[i], offset);
                                offset += this.chunks[i].byteLength;
                                }
                                return result;
                            }
                            process(inputs) {
                                if (inputs[0][0]) {
                                let data = new Uint8Array(this.encoder.encode(inputs[0][0]));
                                this.chunks.push(data);
                                this.chunksByte += data.byteLength;
                                if (this.chunksByte > this.bufferSize) {
                                    let lastChunk = this.chunks[this.chunks.length - 1];
                                    this.chunks[this.chunks.length - 1] = lastChunk.subarray(0, lastChunk.byteLength - this.chunksByte + this.bufferSize);
                                    let chunk = this.concat();
                                    this.port.postMessage(chunk, [chunk.buffer]);
                                    this.chunks.length = 0;
                                    this.chunks.push(lastChunk.subarray(lastChunk.byteLength - this.chunksByte + this.bufferSize));
                                    this.chunksByte = this.chunks[0].byteLength;
                                } else if (this.chunksByte === this.bufferSize) {
                                    let chunk = this.concat();
                                    this.port.postMessage(chunk, [chunk.buffer]);
                                    this.chunks.length = this.chunksByte = 0;
                                }
                                }
                                return this.processing;
                            }
                            }
                            registerProcessor('speech-processor', SP);`;
          this.processScriptURL = URL.createObjectURL(
            new Blob([workletScript], {
              type: "application/javascript; charset=utf-8",
            })
          );
        }
        this.context.audioWorklet
          .addModule(this.processScriptURL)
          .then(() => {
            this.recorder = new AudioWorkletNode(
              this.context,
              "speech-processor",
              {
                processorOptions: { sampleRate: this.context.sampleRate },
              }
            );
            this.ready = true;
            this.recorder.port.onmessage = (e) => {
              if (e.data && this.ready) recSocket.send(this.getRecBin(e.data));
            };
            if (isFirefox) {
              // tested firefox need volume gain
              this.gain = this.context.createGain();
              this.gain.gain.value = 3;
              this.audioInput.connect(this.gain);
              this.gain.connect(this.recorder);
            } else this.audioInput.connect(this.recorder);
            this.recorder.connect(this.context.destination);
            res();
          })
          .catch((e) => {
            this.attachScriptProcessor(res);
          });
      } else this.attachScriptProcessor(res);
    });
  }
  attachScriptProcessor(res) {
    this.encoder = new RiffPcmEncoder(this.context.sampleRate, this.sampleRate);
    this.recorder = (() => {
      let bufferSize = 0;
      try {
        return this.context.createScriptProcessor(bufferSize, 1, 1);
      } catch (error) {
        // Webkit (<= version 31) requires a valid bufferSize.
        bufferSize = 2048;
        let audioSampleRate = this.context.sampleRate;
        while (
          bufferSize < 16384 &&
          audioSampleRate >= this.audioInput.channelCount * this.sampleRate
        ) {
          bufferSize <<= 1;
          audioSampleRate >>= 1;
        }
        return this.context.createScriptProcessor(bufferSize, 1, 1);
      }
    })();
    this.ready = true;
    this.recorder.onaudioprocess = (event) => {
      const inputFrame = event.inputBuffer.getChannelData(0);
      if (inputFrame && this.ready)
        this.pushWSFrame(new Uint8Array(this.encoder.encode(inputFrame)));
    };
    if (isFirefox) {
      // tested firefox need volume gain
      this.gain = this.context.createGain();
      this.gain.gain.value = 3;
      this.audioInput.connect(this.gain);
      this.gain.connect(this.recorder);
    } else this.audioInput.connect(this.recorder);
    this.recorder.connect(this.context.destination);
    res();
  }
  pushWSFrame(audio) {
    let totalByte = this.chunksByte + audio.byteLength;
    if (totalByte >= this.bufferSize) {
      let offset = 0;
      for (let i = 0; i < Math.floor(totalByte / this.bufferSize); i++) {
        if (this.chunksByte) {
          let partAudio = audio.subarray(
            offset,
            (i + 1) * this.bufferSize - this.chunksByte
          );
          this.chunks.push(partAudio);
          let chunk = this.concat();
          recSocket.send(this.getRecBin(chunk));
          offset = (i + 1) * this.bufferSize - this.chunksByte;
          this.chunks.length = this.chunksByte = 0;
        } else {
          let partAudio = audio.subarray(offset, offset + this.bufferSize);
          recSocket.send(this.getRecBin(partAudio));
          offset += this.bufferSize;
        }
      }
      if (offset < audio.byteLength) {
        this.chunks.push(audio.subarray(offset));
        this.chunksByte = this.chunks[0].byteLength;
      }
    } else {
      this.chunks.push(audio);
      this.chunksByte += audio.byteLength;
    }
  }
  concat() {
    let result = new Uint8Array(this.bufferSize);
    let offset = 0;
    for (let i = 0; i < this.chunks.length; i++) {
      result.set(this.chunks[i], offset);
      offset += this.chunks[i].byteLength;
    }
    return result;
  }
  initRecWebsocket() {
    return new Promise((res, rej) => {
      let url = `wss://${azureRecRegion}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?Ocp-Apim-Subscription-Key=${azureRecKey}&language=${select_dialect.value}&storeAudio=true`;
      if (
        !recSocket ||
        recSocket.readyState > 1 ||
        recSocket.url.slice(0, -48) !== url
      ) {
        if (recSocket && recSocket.readyState === 1) recSocket.close(1000);
        recSocket = new WebSocket(url + `&X-ConnectionId=${this.connId}`);
        recSocket.binaryType = "arraybuffer";
        recSocket.onopen = () => {
          res();
        };
        recSocket.onmessage = (e) => {
          this.handleWSMsg(e);
        };
        recSocket.onerror = (e) => {
          if (!this.ready) notyf.error(translations[locale]["badGateTip"]);
          recSocket.close();
          rej();
        };
        recSocket.onclose = (e) => {
          if (this.ready) {
            this.ready = false;
            this.retryWebsocket(recSocket.url);
          }
        };
      } else {
        return res();
      }
    });
  }
  retryWebsocket(url) {
    recSocket = new WebSocket(url);
    recSocket.binaryType = "arraybuffer";
    recSocket.onopen = () => {
      this.startWSDetect();
      this.ready = true;
    };
    recSocket.onmessage = (e) => {
      this.handleWSMsg(e);
    };
    recSocket.onerror = (e) => {
      recSocket.close();
    };
    recSocket.onclose = (e) => {
      notyf.error(translations[locale]["badGateTip"]);
      this.stopRecorder(true);
    };
  }
  handleWSMsg(e) {
    if (typeof e.data === "string") {
      let path = e.data.match(/Path:(.+)/)[1].trim();
      let splitData = e.data.split("\n");
      if (
        this.ready &&
        (path === "speech.phrase" || path === "speech.hypothesis")
      ) {
        let data = JSON.parse(splitData[splitData.length - 1]);
        let isFinal = data.DisplayText !== void 0;
        let autoFlag;
        if (isFinal) {
          recRes += data.DisplayText;
          if (autoSendWord) {
            let idx = recRes.indexOf(autoSendWord);
            if (idx !== -1) {
              recRes = recRes.slice(0, idx);
              autoFlag = 1;
            }
          }
          if (autoStopWord) {
            let idx = recRes.indexOf(autoStopWord);
            if (idx !== -1) {
              recRes = recRes.slice(0, idx);
              autoFlag = 2;
            }
          }
        } else if (data.Text) {
          tempRes = recRes + data.Text;
        }
        inputAreaEle.value = preRes + (isFinal ? recRes : tempRes) + affRes;
        textInputEvent();
        inputAreaEle.focus();
        inputAreaEle.selectionEnd = inputAreaEle.value.length - affRes.length;
        if (autoFlag) {
          if (autoFlag === 1) genFunc();
          else this.stopRecorder(true);
        }
        clearAutoSendTimer();
        if (autoFlag !== 1) setAutoTimer();
      } else if (path === "turn.end") {
        if ((!this.forceStop && keepListenMic) || this.ready) {
          this.startWSDetect();
          this.ready = true;
        }
      }
    }
  }
  startWSDetect() {
    this.reqId = uuidv4(true);
    recSocket.send(this.getRecPre(this.label));
    recSocket.send(this.getRecConfig());
    recSocket.send(this.getRecPreBin());
  }
  getRecPre(label) {
    let osPlatform = typeof window !== "undefined" ? "Browser" : "Node";
    osPlatform += "/" + navigator.platform;
    let osName = navigator.userAgent;
    let osVersion = navigator.appVersion;
    return `Path: speech.config\r\nX-RequestId: ${
      this.reqId
    }\r\nX-Timestamp: ${new Date().toISOString()}\r\nContent-Type: application/json\r\n\r\n{"context":{"system":{"name":"SpeechSDK","version":"1.35.0","build":"JavaScript","lang":"JavaScript"},"os":{"platform":"${osPlatform}","name":"${osName}","version":"${osVersion}"},"audio":{"source":{"bitspersample":16,"channelcount":1,"connectivity":"Unknown","manufacturer":"Speech SDK","model":"${label}","samplerate":${
      this.sampleRate
    },"type":"Microphones"}}},"recognition":"conversation"}`;
  }
  getRecConfig() {
    return `Path: speech.context\r\nX-RequestId: ${
      this.reqId
    }\r\nX-Timestamp: ${new Date().toISOString()}\r\nContent-Type: application/json\r\n\r\n{"phraseDetection":{}}`;
  }
  getRecPreBin() {
    let header = this.getRecHeader();
    let data = new Uint8Array(2 + header.length + this.wavHeader.byteLength);
    data.set([0, 126], 0);
    data.set(stringToArray(header), 2);
    data.set(this.wavHeader, 2 + header.length);
    return data;
  }
  getRecBin(audio) {
    let header = this.getRecHeader();
    let data = new Uint8Array(2 + header.length + audio.byteLength);
    data.set([0, 99], 0);
    data.set(stringToArray(header), 2);
    data.set(audio, 2 + header.length);
    return data;
  }
  getRecHeader() {
    return `Path: audio\r\nX-RequestId: ${
      this.reqId
    }\r\nX-Timestamp: ${new Date().toISOString()}\r\n`;
  }
  getMedia() {
    return new Promise((res, rej) => {
      navigator.mediaDevices
        .getUserMedia({ audio: true })
        .then((stream) => {
          this.stream = stream;
          res();
        })
        .catch((e) => {
          notyf.error(
            translations[locale][
              e.name === "NotAllowedError" ? "noMicPerTip" : "noMicTip"
            ]
          );
          rej();
        });
    });
  }
  async startRecorder() {
    return new Promise((res, rej) => {
      this.connId = uuidv4(true);
      Promise.all([this.getMedia(), this.initRecWebsocket()])
        .then((val) => {
          this.label = this.stream.getAudioTracks()[0].label;
          this.startWSDetect();
          this.initRecorder().then(() => {
            res();
          });
        })
        .catch((e) => {
          if (this.stream) {
            this.stream.getAudioTracks().forEach((track) => {
              track.stop();
            });
            this.stream = null;
          }
          rej(e);
        });
    });
  }
  stopRecWebsocket() {
    if (recSocket && recSocket.readyState === 1) {
      let endBin = this.getRecBin(new Uint8Array());
      recSocket.send(endBin);
      recSocket.send(endBin);
    }
  }
  stopRecorder(forceStop) {
    this.forceStop = forceStop;
    this.ready = false;
    this.stopRecWebsocket();
    clearAutoSendTimer();
    if (!forceStop && keepListenMic) return;
    voiceRecEle.classList.remove("voiceRecing");
    recing = false;
    if (this.recorder && this.recorder.port) {
      this.recorder.port.postMessage("stop");
      this.recorder.port.close();
    }
    if (this.stream) {
      this.stream.getAudioTracks().forEach((track) => {
        track.stop();
      });
      this.stream = null;
    }
    if (this.audioInput) {
      this.audioInput.disconnect();
      this.audioInput = null;
    }
    if (isFirefox && this.gain) {
      this.gain.disconnect();
      this.gain = null;
    }
    if (this.recorder) {
      this.recorder.disconnect();
      this.recorder = null;
    }
    if (this.context) {
      this.context.close();
      this.context = null;
    }
  }
}
class LegacyRecorder {
  constructor() {
    this.mimeType = MediaRecorder.isTypeSupported("audio/mp4")
      ? "audio/mp4"
      : "audio/webm";
    this.suffix = this.mimeType === "audio/mp4" ? ".mp4" : ".webm";
    this.bitsPerSecond = 128000;
    this.chunks = [];
  }
  initRecorder() {
    this.recorder = new MediaRecorder(this.stream, {
      mimeType: this.mimeType,
      audioBitsPerSecond: this.bitsPerSecond,
    });
    this.chunks.length = 0;
    this.recorder.ondataavailable = (e) => {
      this.chunks.push(e.data);
    };
    this.recorder.start(1e3);
  }
  async processData(blob) {
    let formData = new FormData();
    formData.append("model", "whisper-1");
    formData.append("file", blob, "audio" + this.suffix);
    if (select_dialect.value !== "")
      formData.append("language", select_dialect.value);
    let url =
      apiHost +
      (apiHost.length && !apiHost.endsWith("/") ? "/" : "") +
      "v1/audio/transcriptions";
    let controller = new AbortController();
    let controllerId = setTimeout(() => {
      notyf.error(translations[locale]["timeoutTip"]);
      controller.abort();
    }, 15000);
    try {
      const res = await fetch(url, {
        method: "POST",
        body: formData,
        signal: controller.signal,
        ...(customAPIKey
          ? { headers: { Authorization: "Bearer " + customAPIKey } }
          : {}),
      });
      clearTimeout(controllerId);
      if (res.status === 200) {
        let result = await res.json();
        if (result && result.text) {
          inputAreaEle.value = preRes + result.text + affRes;
          textInputEvent();
          inputAreaEle.focus();
          inputAreaEle.selectionEnd = inputAreaEle.value.length - affRes.length;
          return true;
        }
      } else
        notyf.open({
          type: "warning",
          message: translations[locale]["cantTranscribeTip"],
        });
    } catch (e) {}
  }
  getMedia() {
    return new Promise((res, rej) => {
      navigator.mediaDevices
        .getUserMedia({ audio: true })
        .then((stream) => {
          this.stream = stream;
          res();
        })
        .catch((e) => {
          notyf.error(
            translations[locale][
              e.name === "NotAllowedError" ? "noMicPerTip" : "noMicTip"
            ]
          );
          rej(e);
        });
    });
  }
  async startRecorder() {
    return new Promise((res, rej) => {
      this.getMedia()
        .then(() => {
          this.initRecorder();
          res();
        })
        .catch((e) => {
          rej(e);
        });
    });
  }
  stopRecorder(forceStop) {
    return new Promise((res, rej) => {
      clearAutoSendTimer();
      if (this.recorder && this.recorder.state === "recording") {
        this.recorder.onstop = async (e) => {
          let blob = new Blob(this.chunks, { type: this.mimeType });
          this.chunks.length = 0;
          try {
            let result = await this.processData(blob);
            if (result) res();
            else rej();
          } catch (error) {
            rej();
          }
          if (!forceStop && keepListenMic) this.recorder.start(1e3);
        };
        this.recorder.stop();
      } else res();
      if (!forceStop && keepListenMic) return;
      voiceRecEle.classList.remove("voiceRecing");
      recing = false;
      if (this.recorder) this.recorder = null;
      if (this.stream) {
        this.stream.getAudioTracks().forEach((track) => {
          track.stop();
        });
        this.stream = null;
      }
    });
  }
}
let recorder, legacyRecorder, initingRecorder;
let recRes = (tempRes = "");
let preRes, affRes;
resetRecRes = () => {
  preRes = inputAreaEle.value.slice(0, inputAreaEle.selectionStart);
  affRes = inputAreaEle.value.slice(inputAreaEle.selectionEnd);
  recRes = tempRes = "";
};
const initRecEvent = () => {
  if (existRec === 3) {
    if (legacyRecorder === void 0) legacyRecorder = new LegacyRecorder();
    toggleRecEv = async (force = true) => {
      if (voiceRecEle.classList.contains("voiceRecing"))
        await legacyRecorder.stopRecorder(force);
      else {
        if (initingRecorder) return;
        resetRecRes();
        initingRecorder = true;
        legacyRecorder
          .startRecorder()
          .then(() => {
            recing = true;
            initingRecorder = false;
            voiceRecEle.classList.add("voiceRecing");
          })
          .catch((e) => {
            legacyRecorder.stopRecorder(force);
            initingRecorder = false;
          });
      }
    };
  } else if (existRec === 2) {
    if (recorder === void 0) recorder = new Recorder();
    toggleRecEv = (force = true) => {
      if (voiceRecEle.classList.contains("voiceRecing"))
        recorder.stopRecorder(force);
      else {
        if (initingRecorder) return;
        resetRecRes();
        initingRecorder = true;
        recorder
          .startRecorder()
          .then(() => {
            recing = true;
            initingRecorder = false;
            voiceRecEle.classList.add("voiceRecing");
          })
          .catch((e) => {
            recorder.stopRecorder(force);
            initingRecorder = false;
          });
      }
    };
  } else {
    let recIns = new webkitSpeechRecognition();
    // prevent some Android bug
    recIns.continuous = !isAndroid;
    recIns.interimResults = true;
    recIns.maxAlternatives = 1;
    let resEvent = (event) => {
      if (typeof event.results === "undefined") {
        toggleRecEv();
        return;
      }
      let isFinal;
      let autoFlag;
      for (let i = event.resultIndex; i < event.results.length; ++i) {
        isFinal = event.results[i].isFinal;
        if (isFinal) {
          recRes += event.results[i][0].transcript;
          if (autoSendWord) {
            let idx = recRes.indexOf(autoSendWord);
            if (idx !== -1) {
              recRes = recRes.slice(0, idx);
              autoFlag = 1;
              break;
            }
          }
          if (autoStopWord) {
            let idx = recRes.indexOf(autoStopWord);
            if (idx !== -1) {
              recRes = recRes.slice(0, idx);
              autoFlag = 2;
              break;
            }
          }
        } else {
          tempRes = recRes + event.results[i][0].transcript;
        }
      }
      inputAreaEle.value = preRes + (isFinal ? recRes : tempRes) + affRes;
      textInputEvent();
      inputAreaEle.focus();
      inputAreaEle.selectionEnd = inputAreaEle.value.length - affRes.length;
      if (autoFlag) {
        if (autoFlag === 1) genFunc();
        else endEvent(false, false);
      }
      clearAutoSendTimer();
      if (autoFlag !== 1) setAutoTimer();
    };
    const stopAction = () => {
      clearAutoSendTimer();
      recIns.onresult = null;
      recIns.onerror = null;
      recIns.onend = null;
      voiceRecEle.classList.remove("voiceRecing");
      recing = false;
    };
    const endEvent = (event, flag) => {
      if (flag !== void 0) {
        if (!flag) {
          recIns.stop();
          stopAction();
        }
      } else if (event) {
        if (keepListenMic && event.type === "end") {
          recIns.start();
          resetRecRes();
        } else {
          if (event.type === "error") recIns.stop();
          stopAction();
        }
      }
    };
    const errorEvent = (ev) => {
      if (event.error === "no-speech") {
        notyf.open({
          type: "warning",
          message: translations[locale]["noSpeechTip"],
        });
      }
      if (event.error === "audio-capture") {
        notyf.error(translations[locale]["noMicTip"]);
        endEvent(ev);
      }
      if (event.error === "not-allowed") {
        notyf.error(translations[locale]["noMicPerTip"]);
        endEvent(ev);
      }
    };
    toggleRecEv = () => {
      if (voiceRecEle.classList.toggle("voiceRecing")) {
        try {
          resetRecRes();
          recIns.lang = select_dialect.value;
          recIns.start();
          recIns.onresult = resEvent;
          recIns.onerror = errorEvent;
          recIns.onend = endEvent;
          recing = true;
        } catch (e) {
          endEvent(false, false);
        }
      } else {
        endEvent(false, false);
      }
    };
  }
};
recServiceEle.dispatchEvent(new Event("change"));
document.querySelector(".modelSwitch").onclick =
  document.querySelector(".sysSwitch").onclick =
  document.querySelector(".setSwitch").onclick =
    function (ev) {
      let activeEle = this.getElementsByClassName("activeSwitch")[0];
      if (ev.target !== activeEle) {
        activeEle.classList.remove("activeSwitch");
        ev.target.classList.add("activeSwitch");
        document.getElementById(ev.target.dataset.id).style.display = "block";
        document.getElementById(activeEle.dataset.id).style.display = "none";
      }
    };
if (!supportSpe) speechServiceEle.remove(3);
const initVoiceVal = () => {
  let localVoiceType = localStorage.getItem("existVoice");
  speechServiceEle.value = existVoice = parseInt(localVoiceType || "2");
};
initVoiceVal();
const clearAzureVoice = () => {
  azureKey = void 0;
  localStorage.removeItem(azureRegion + "VoiceData");
  azureRegion = void 0;
  azureRole = [];
  azureStyle = [];
  document.getElementById("azureExtra").style.display = "none";
  azureKeyInput.parentElement.style.display = "none";
  preSetAzureRegion.parentElement.style.display = "none";
};
speechServiceEle.onchange = () => {
  existVoice = parseInt(speechServiceEle.value);
  localStorage.setItem("existVoice", existVoice);
  toggleVoiceCheck(true);
  if (checkAzureAbort && !checkAzureAbort.signal.aborted) {
    checkAzureAbort.abort();
    checkAzureAbort = void 0;
  }
  if (checkEdgeAbort && !checkEdgeAbort.signal.aborted) {
    checkEdgeAbort.abort();
    checkEdgeAbort = void 0;
  }
  if (existVoice === 4) {
    toggleVoiceCheck(false);
    clearAzureVoice();
    loadOpenAIVoice();
  } else if (existVoice === 3) {
    azureKeyInput.parentElement.style.display = "block";
    preSetAzureRegion.parentElement.style.display = "block";
    loadAzureVoice();
  } else if (existVoice === 2) {
    clearAzureVoice();
    loadEdgeVoice();
  } else if (existVoice === 1) {
    toggleVoiceCheck(false);
    clearAzureVoice();
    loadLocalVoice();
  }
};
let openaiVoiceData,
  edgeVoiceData,
  systemVoiceData,
  checkAzureAbort,
  checkEdgeAbort;
const toggleVoiceCheck = (bool) => {
  checkVoiceLoad.style.display = bool ? "flex" : "none";
  speechDetail.style.display = bool ? "none" : "block";
};
const loadOpenAIVoice = () => {
  if (openaiVoiceData) {
    initVoiceSetting(openaiVoiceData);
  } else {
    openaiVoiceData = [
      { name: "alloy", displayName: "alloy" },
      { name: "echo", displayName: "echo" },
      { name: "fable", displayName: "fable" },
      { name: "onyx", displayName: "onyx" },
      { name: "nova", displayName: "nova" },
      { name: "shimmer", displayName: "shimmer" },
    ];
    initVoiceSetting(openaiVoiceData);
  }
};
const loadAzureVoice = () => {
  let checking = false;
  const checkAzureFunc = () => {
    if (checking) return;
    if (azureKey) {
      checking = true;
      checkVoiceLoad.classList.add("voiceChecking");
      checkAzureAbort = new AbortController();
      setTimeout(() => {
        if (checkAzureAbort && !checkAzureAbort.signal.aborted) {
          checkAzureAbort.abort();
          checkAzureAbort = void 0;
        }
      }, 15000);
      getAzureToken(checkAzureAbort.signal)
        .then(() => {
          getVoiceList(checkAzureAbort.signal)
            .then(() => {
              toggleVoiceCheck(false);
            })
            .catch((e) => {})
            .finally(() => {
              checkVoiceLoad.classList.remove("voiceChecking");
              checking = false;
            });
        })
        .catch((e) => {})
        .finally(() => {
          checkVoiceLoad.classList.remove("voiceChecking");
          checking = false;
        });
    }
  };
  checkVoiceLoad.onclick = checkAzureFunc;
  const getAzureToken = (signal) => {
    return new Promise((res, rej) => {
      fetch(
        "https://" +
          azureRegion +
          ".api.cognitive.microsoft.com/sts/v1.0/issueToken",
        {
          signal,
          method: "POST",
          headers: {
            "Ocp-Apim-Subscription-Key": azureKey,
          },
        }
      )
        .then((response) => {
          response.text().then((text) => {
            try {
              let json = JSON.parse(text);
              notyf.error(translations[locale]["azureInvalidTip"]);
              rej();
            } catch (e) {
              res();
            }
          });
        })
        .catch((e) => {
          localStorage.removeItem(azureRegion + "VoiceData");
          rej();
        });
    });
  };
  const getVoiceList = (signal) => {
    return new Promise((res, rej) => {
      let localAzureVoiceData = localStorage.getItem(azureRegion + "VoiceData");
      if (localAzureVoiceData) {
        initVoiceSetting(JSON.parse(localAzureVoiceData));
        res();
      } else {
        fetch(
          "https://" +
            azureRegion +
            ".tts.speech.microsoft.com/cognitiveservices/voices/list",
          {
            signal,
            headers: {
              "Ocp-Apim-Subscription-Key": azureKey,
            },
          }
        )
          .then((response) => {
            response
              .json()
              .then((json) => {
                localStorage.setItem(
                  azureRegion + "VoiceData",
                  JSON.stringify(json)
                );
                initVoiceSetting(json);
                res();
              })
              .catch((e) => {
                notyf.error(translations[locale]["azureInvalidTip"]);
                rej();
              });
          })
          .catch((e) => {
            localStorage.removeItem(azureRegion + "VoiceData");
            rej();
          });
      }
    });
  };
  let azureRegionEle = document.getElementById("preSetAzureRegion");
  if (!azureRegionEle.options.length) {
    azureRegions.forEach((region, i) => {
      let option = document.createElement("option");
      option.value = region;
      option.text = region;
      azureRegionEle.options.add(option);
    });
  }
  let localAzureRegion = localStorage.getItem("azureRegion");
  if (localAzureRegion) {
    azureRegion = localAzureRegion;
    azureRegionEle.value = localAzureRegion;
  }
  azureRegionEle.onchange = () => {
    azureRegion = azureRegionEle.value;
    localStorage.setItem("azureRegion", azureRegion);
    toggleVoiceCheck(true);
  };
  azureRegionEle.dispatchEvent(new Event("change"));
  let azureKeyEle = document.getElementById("azureKeyInput");
  let localAzureKey = localStorage.getItem("azureKey");
  if (localAzureKey) {
    azureKey = localAzureKey;
    azureKeyEle.value = localAzureKey;
  }
  azureKeyEle.onchange = () => {
    azureKey = azureKeyEle.value;
    localStorage.setItem("azureKey", azureKey);
    toggleVoiceCheck(true);
  };
  azureKeyEle.dispatchEvent(new Event("change"));
  if (azureKey) {
    checkAzureFunc();
  }
};
const loadEdgeVoice = () => {
  let checking = false;
  const endCheck = () => {
    checkVoiceLoad.classList.remove("voiceChecking");
    checking = false;
  };
  const checkEdgeFunc = () => {
    if (checking) return;
    checking = true;
    checkVoiceLoad.classList.add("voiceChecking");
    if (edgeVoiceData) {
      initVoiceSetting(edgeVoiceData);
      toggleVoiceCheck(false);
      endCheck();
    } else {
      checkEdgeAbort = new AbortController();
      setTimeout(() => {
        if (checkEdgeAbort && !checkEdgeAbort.signal.aborted) {
          checkEdgeAbort.abort();
          checkEdgeAbort = void 0;
        }
      }, 10000);
      fetch(
        "https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4",
        { signal: checkEdgeAbort.signal }
      )
        .then((response) => {
          response.json().then((json) => {
            edgeVoiceData = json;
            toggleVoiceCheck(false);
            initVoiceSetting(json);
            endCheck();
          });
        })
        .catch((err) => {
          endCheck();
        });
    }
  };
  checkEdgeFunc();
  checkVoiceLoad.onclick = checkEdgeFunc;
};
const loadLocalVoice = () => {
  if (systemVoiceData) {
    initVoiceSetting(systemVoiceData);
  } else {
    let initedVoice = false;
    const getLocalVoice = () => {
      let voices = speechSynthesis.getVoices();
      if (voices.length) {
        if (!initedVoice) {
          initedVoice = true;
          systemVoiceData = voices;
          initVoiceSetting(voices);
        }
        return true;
      } else {
        return false;
      }
    };
    let syncExist = getLocalVoice();
    if (!syncExist) {
      if ("onvoiceschanged" in speechSynthesis) {
        speechSynthesis.onvoiceschanged = () => {
          getLocalVoice();
        };
      } else if (speechSynthesis.addEventListener) {
        speechSynthesis.addEventListener("voiceschanged", () => {
          getLocalVoice();
        });
      }
      let timeout = 0;
      let timer = setInterval(() => {
        if (getLocalVoice() || timeout > 1000) {
          if (timeout > 1000) {
            existVoice = 0;
          }
          clearInterval(timer);
          timer = null;
        }
        timeout += 300;
      }, 300);
    }
  }
};
const voicesEle = document.getElementById("preSetSpeech");
const initVoiceSetting = (voices) => {
  if (existVoice < 4) {
    let isOnline = existVoice === 2 || existVoice === 3;
    // 支持中文和英文
    voices = isOnline
      ? voices.filter((item) => item.Locale.match(/^(zh-|en-)/))
      : voices.filter((item) => item.lang.match(/^(zh-|en-)/));
    if (isOnline) {
      voices.map((item) => {
        item.name =
          item.FriendlyName ||
          `${item.DisplayName} Online (${item.VoiceType}) - ${item.LocaleName}`;
        item.lang = item.Locale;
      });
    } else if (isSafari && voices[0].voiceURI.startsWith("com.apple")) {
      voices = voices.filter((item) => {
        return !item.voiceURI.startsWith("com.apple.voice.super-compact");
      });
    }
    voices.sort((a, b) => {
      if (a.lang.slice(0, 2) === b.lang.slice(0, 2)) {
        if (a.lang.slice(0, 2) === "zh") {
          return a.lang === b.lang ? 0 : a.lang > b.lang ? 1 : -1; // zh-CN 在前
        } else {
          return 0;
        }
      }
      return (locale === "zh" ? a.lang < b.lang : a.lang > b.lang) ? 1 : -1; // 中文UI，则中文"z"在前
    });
    voices.map((item) => {
      if (item.name.match(/^(Google |Microsoft )/)) {
        item.displayName = item.name.replace(/^.*? /, "");
      } else {
        item.displayName = item.name;
      }
    });
    if (isSafari && !isOnline) {
      voices.map((item) => {
        item.displayName = `${item.name} (${item.lang})`;
      });
    }
  }
  voicesData = voices;
  voicesEle.innerHTML = "";
  voices.forEach((voice, i) => {
    let option = document.createElement("option");
    option.value = i;
    option.text = voice.displayName;
    voicesEle.options.add(option);
  });
  const loadAnother = (type) => {
    type = type ^ 1;
    let localVoice = localStorage.getItem("voice" + type);
    if (localVoice) {
      let localIndex = voices.findIndex((item) => {
        return item.name === localVoice;
      });
      if (localIndex === -1) localIndex = 0;
      voiceRole[type] = voices[localIndex];
    } else {
      voiceRole[type] = voices[0];
    }
    if (existVoice === 3) {
      let localStyle = localStorage.getItem("azureStyle" + type);
      azureStyle[type] = localStyle ? localStyle : void 0;
      let localRole = localStorage.getItem("azureRole" + type);
      azureRole[type] = localRole ? localRole : void 0;
    }
  };
  voiceChange();
  loadAnother(voiceType);
};
let voiceChange;
const initVoiceFunc = () => {
  voicesEle.onchange = () => {
    voiceRole[voiceType] = voicesData[voicesEle.value];
    localStorage.setItem("voice" + voiceType, voiceRole[voiceType].name);
    if (voiceRole[voiceType].StyleList || voiceRole[voiceType].RolePlayList) {
      document.getElementById("azureExtra").style.display = "block";
      let voiceStyles = voiceRole[voiceType].StyleList;
      let voiceRoles = voiceRole[voiceType].RolePlayList;
      if (voiceRoles) {
        preSetVoiceRole.innerHTML = "";
        let option = document.createElement("option");
        option.value = "Default";
        option.text = "Default";
        preSetVoiceRole.options.add(option);
        voiceRoles.forEach((role, i) => {
          let option = document.createElement("option");
          option.value = role;
          option.text = role;
          preSetVoiceRole.options.add(option);
        });
        let localRole = localStorage.getItem("azureRole" + voiceType);
        if (localRole && voiceRoles.indexOf(localRole) !== -1) {
          preSetVoiceRole.value = localRole;
          azureRole[voiceType] = localRole;
        } else {
          preSetVoiceRole.selectedIndex = 0;
          azureRole[voiceType] = voiceRole[0];
        }
        preSetVoiceRole.onchange = () => {
          azureRole[voiceType] = preSetVoiceRole.value;
          localStorage.setItem("azureRole" + voiceType, preSetVoiceRole.value);
        };
        preSetVoiceRole.dispatchEvent(new Event("change"));
      } else {
        azureRole[voiceType] = void 0;
        localStorage.removeItem("azureRole" + voiceType);
      }
      preSetVoiceRole.style.display = voiceRoles ? "block" : "none";
      preSetVoiceRole.previousElementSibling.style.display = voiceRoles
        ? "block"
        : "none";
      if (voiceStyles) {
        preSetVoiceStyle.innerHTML = "";
        let option = document.createElement("option");
        option.value = "Default";
        option.text = "Default";
        preSetVoiceStyle.options.add(option);
        voiceStyles.forEach((style, i) => {
          let option = document.createElement("option");
          option.value = style;
          option.text = style;
          preSetVoiceStyle.options.add(option);
        });
        let localStyle = localStorage.getItem("azureStyle" + voiceType);
        if (localStyle && voiceStyles.indexOf(localStyle) !== -1) {
          preSetVoiceStyle.value = localStyle;
          azureStyle[voiceType] = localStyle;
        } else {
          preSetVoiceStyle.selectedIndex = 0;
          azureStyle[voiceType] = voiceStyles[0];
        }
        preSetVoiceStyle.onchange = () => {
          azureStyle[voiceType] = preSetVoiceStyle.value;
          localStorage.setItem(
            "azureStyle" + voiceType,
            preSetVoiceStyle.value
          );
        };
        preSetVoiceStyle.dispatchEvent(new Event("change"));
      } else {
        azureStyle[voiceType] = void 0;
        localStorage.removeItem("azureStyle" + voiceType);
      }
      preSetVoiceStyle.style.display = voiceStyles ? "block" : "none";
      preSetVoiceStyle.previousElementSibling.style.display = voiceStyles
        ? "block"
        : "none";
    } else {
      document.getElementById("azureExtra").style.display = "none";
      azureRole[voiceType] = void 0;
      localStorage.removeItem("azureRole" + voiceType);
      azureStyle[voiceType] = void 0;
      localStorage.removeItem("azureStyle" + voiceType);
    }
  };
  voiceChange = () => {
    let localVoice = localStorage.getItem("voice" + voiceType);
    if (localVoice) {
      let localIndex = voicesData.findIndex((item) => {
        return item.name === localVoice;
      });
      if (localIndex === -1) localIndex = 0;
      voiceRole[voiceType] = voicesData[localIndex];
      voicesEle.value = localIndex;
    } else {
      voiceRole[voiceType] = voicesData[0];
    }
    voicesEle.dispatchEvent(new Event("change"));
  };
  let volumeEle = document.getElementById("voiceVolume");
  let localVolume = localStorage.getItem("voiceVolume0");
  voiceVolume[0] = parseFloat(localVolume || volumeEle.getAttribute("value"));
  const voiceVolumeChange = () => {
    let localVolume = localStorage.getItem("voiceVolume" + voiceType);
    volumeEle.value = voiceVolume[voiceType] = parseFloat(
      localVolume || volumeEle.getAttribute("value")
    );
    volumeEle.style.backgroundSize =
      ((volumeEle.value - volumeEle.min) * 100) /
        (volumeEle.max - volumeEle.min) +
      "% 100%";
  };
  volumeEle.oninput = () => {
    volumeEle.style.backgroundSize =
      ((volumeEle.value - volumeEle.min) * 100) /
        (volumeEle.max - volumeEle.min) +
      "% 100%";
    voiceVolume[voiceType] = parseFloat(volumeEle.value);
    localStorage.setItem("voiceVolume" + voiceType, volumeEle.value);
  };
  voiceVolumeChange();
  let rateEle = document.getElementById("voiceRate");
  let localRate = localStorage.getItem("voiceRate0");
  voiceRate[0] = parseFloat(localRate || rateEle.getAttribute("value"));
  const voiceRateChange = () => {
    let localRate = localStorage.getItem("voiceRate" + voiceType);
    rateEle.value = voiceRate[voiceType] = parseFloat(
      localRate || rateEle.getAttribute("value")
    );
    rateEle.style.backgroundSize =
      ((rateEle.value - rateEle.min) * 100) / (rateEle.max - rateEle.min) +
      "% 100%";
  };
  rateEle.oninput = () => {
    rateEle.style.backgroundSize =
      ((rateEle.value - rateEle.min) * 100) / (rateEle.max - rateEle.min) +
      "% 100%";
    voiceRate[voiceType] = parseFloat(rateEle.value);
    localStorage.setItem("voiceRate" + voiceType, rateEle.value);
  };
  voiceRateChange();
  let pitchEle = document.getElementById("voicePitch");
  let localPitch = localStorage.getItem("voicePitch0");
  voicePitch[0] = parseFloat(localPitch || pitchEle.getAttribute("value"));
  const voicePitchChange = () => {
    let localPitch = localStorage.getItem("voicePitch" + voiceType);
    pitchEle.value = voicePitch[voiceType] = parseFloat(
      localPitch || pitchEle.getAttribute("value")
    );
    pitchEle.style.backgroundSize =
      ((pitchEle.value - pitchEle.min) * 100) / (pitchEle.max - pitchEle.min) +
      "% 100%";
  };
  pitchEle.oninput = () => {
    pitchEle.style.backgroundSize =
      ((pitchEle.value - pitchEle.min) * 100) / (pitchEle.max - pitchEle.min) +
      "% 100%";
    voicePitch[voiceType] = parseFloat(pitchEle.value);
    localStorage.setItem("voicePitch" + voiceType, pitchEle.value);
  };
  voicePitchChange();
  document.getElementById("voiceTypes").onclick = (ev) => {
    let type = ev.target.dataset.type;
    if (type !== void 0) {
      type = parseInt(type);
      if (type != voiceType) {
        voiceType = type;
        ev.target.classList.add("selVoiceType");
        ev.target.parentElement.children[type ^ 1].classList.remove(
          "selVoiceType"
        );
        voiceChange();
        voiceVolumeChange();
        voiceRateChange();
        voicePitchChange();
      }
    }
  };
  const voiceTestEle = document.getElementById("testVoiceText");
  let localTestVoice = localStorage.getItem("voiceTestText");
  voiceTestText = voiceTestEle.value =
    localTestVoice || voiceTestEle.getAttribute("value");
  voiceTestEle.oninput = () => {
    voiceTestText = voiceTestEle.value;
    localStorage.setItem("voiceTestText", voiceTestText);
  };
  const contVoiceEle = document.getElementById("enableContVoice");
  let localCont = localStorage.getItem("enableContVoice");
  contVoiceEle.checked = enableContVoice =
    (localCont || contVoiceEle.getAttribute("checked")) === "true";
  contVoiceEle.onchange = () => {
    enableContVoice = contVoiceEle.checked;
    localStorage.setItem("enableContVoice", enableContVoice);
  };
  contVoiceEle.dispatchEvent(new Event("change"));
  const autoVoiceEle = document.getElementById("enableAutoVoice");
  let localAuto = localStorage.getItem("enableAutoVoice");
  autoVoiceEle.checked = enableAutoVoice =
    (localAuto || autoVoiceEle.getAttribute("checked")) === "true";
  autoVoiceEle.onchange = () => {
    enableAutoVoice = autoVoiceEle.checked;
    localStorage.setItem("enableAutoVoice", enableAutoVoice);
  };
  autoVoiceEle.dispatchEvent(new Event("change"));
};
initVoiceFunc();
speechServiceEle.dispatchEvent(new Event("change"));
const API_URL = "v1/chat/completions";
const Gemini_API_URL = "v1/models/";
const Claude_API_URL = "v1/messages";
let loading = false;
let presetRoleData = {
  default: translations[locale]["defaultText"],
  normal: translations[locale]["assistantText"],
  cat: translations[locale]["catText"],
  emoji: translations[locale]["emojiText"],
  image: translations[locale]["imageText"],
};
let modelVersion; // 模型版本
let modelType; // 1:ChatGPT，2:Gemini，3:Claude
let apiHost; // api反代地址
let geminiApiHost;
let claudeApiHost;
let apiSelects = []; // api地址列表
let geminiApiSelects = [];
let claudeApiSelects = [];
let customAPIKey; // 自定义apiKey
let geminiAPIKey;
let claudeAPIKey;
let customAPIModel; // 自定义apiModel
let geminiAPIModel;
let claudeAPIModel;
let systemRole; // 自定义系统角色
let roleNature; // 角色性格
let roleTemp; // 回答质量
let convWidth = []; // 会话宽度，0:窗口宽度，1:全屏宽度
let textSpeed; // 打字机速度，越小越快
let contLen; // 连续会话长度，默认25，对话包含25条上下文信息。设置为0即关闭连续会话
let enableLongReply; // 是否开启长回复，默认关闭，开启可能导致api费用增加。
let longReplyFlag;
let voiceIns; // Audio or SpeechSynthesisUtterance
const isFirefox = !!navigator.userAgent.match(/firefox/i);
const supportMSE = !!window.MediaSource && !isFirefox; // 是否支持MSE（除了ios应该都支持）
const voiceMIME = "audio/mpeg";
const voiceFormat = "audio-24khz-48kbitrate-mono-mp3";
const voicePreLen = 130;
const voiceSuffix = ".mp3";
const openAIVoiceSuffix = ".aac";
let userAvatar; // 用户头像
let customDarkOut;
let isCaseSearch; // 搜索是否区分大小写
let controller;
let controllerId;
const findOffsetTop = (ele, target) => {
  // target is positioned ancestor element
  if (ele.offsetParent !== target)
    return ele.offsetTop + findOffsetTop(ele.offsetParent, target);
  else return ele.offsetTop;
};
const findResEle = (ele) => {
  if (!ele.classList.contains("response")) return findResEle(ele.parentElement);
  else return ele;
};
const isContentBottom = (ele) => {
  if (refreshIdx !== void 0) {
    return (
      currentResEle.clientHeight + currentResEle.offsetTop >
      messagesEle.scrollTop + messagesEle.clientHeight
    );
  } else {
    return (
      messagesEle.scrollHeight -
        messagesEle.scrollTop -
        messagesEle.clientHeight <
      128
    );
  }
};
const isEleBottom = (ele) => {
  return (
    ele.clientHeight + findOffsetTop(ele, messagesEle) >
    messagesEle.scrollTop + messagesEle.clientHeight
  );
};
const outOfMsgWindow = (ele) => {
  return (
    ele.offsetTop > messagesEle.scrollTop + messagesEle.clientHeight ||
    ele.offsetTop + ele.clientHeight < messagesEle.scrollTop
  );
};
const scrollToBottom = () => {
  if (isContentBottom()) {
    if (refreshIdx !== void 0) {
      messagesEle.scrollTo(
        0,
        currentResEle.clientHeight +
          currentResEle.offsetTop -
          messagesEle.clientHeight +
          10
      );
    } else {
      messagesEle.scrollTo(0, messagesEle.scrollHeight);
    }
  }
};
const scrollToBottomLoad = (ele) => {
  if (!controller || !ele.offsetParent) return;
  if (isEleBottom(ele)) {
    let resEle = findResEle(ele);
    messagesEle.scrollTo(
      0,
      resEle.clientHeight + resEle.offsetTop - messagesEle.clientHeight + 10
    );
  }
};
const forceRepaint = (ele) => {
  ele.style.display = "none";
  ele.offsetHeight;
  ele.style.display = null;
};
const escapeTextarea = document.createElement("textarea");
const getEscape = (str) => {
  escapeTextarea.textContent = str;
  return escapeTextarea.innerHTML;
};
const parser = new DOMParser();
const getUnescape = (html) => {
  return parser.parseFromString(html, "text/html").body.innerText;
};
const escapeRegexExp = (str) => {
  // from vscode src/vs/base/common/strings.ts escapeRegExpCharacters
  return str.replace(/[\\\{\}\*\+\?\|\^\$\.\[\]\(\)]/g, "\\$&");
};
const checkStorage = () => {
  let used = 0;
  for (let key in localStorage) {
    localStorage.hasOwnProperty(key) && (used += localStorage[key].length);
  }
  let remain = 5242880 - used;
  usedStorageBar.style.width = ((used / 5242880) * 100).toFixed(2) + "%";
  let usedMBs = used / 1048576;
  usedStorage.textContent =
    (usedMBs < 1 ? usedMBs.toPrecision(2) : usedMBs.toFixed(2)) + "MB";
  availableStorage.textContent =
    Math.floor((remain / 1048576) * 100) / 100 + "MB";
};
const UNESCAPE_RE = /\\([ \\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;
const superscript = (state, silent) => {
  let found,
    content,
    token,
    max = state.posMax,
    start = state.pos;
  if (state.src.charCodeAt(start) !== 0x5e /* ^ */) {
    return false;
  }
  if (silent) {
    return false;
  } // don't run any pairs in validation mode
  if (start + 2 >= max) {
    return false;
  }
  state.pos = start + 1;
  while (state.pos < max) {
    if (state.src.charCodeAt(state.pos) === 0x5e /* ^ */) {
      found = true;
      break;
    }
    state.md.inline.skipToken(state);
  }
  if (!found || start + 1 === state.pos) {
    state.pos = start;
    return false;
  }
  content = state.src.slice(start + 1, state.pos);
  // don't allow unescaped spaces/newlines inside
  if (content.match(/(^|[^\\])(\\\\)*\s/)) {
    state.pos = start;
    return false;
  }
  // found!
  state.posMax = state.pos;
  state.pos = start + 1;
  // Earlier we checked !silent, but this implementation does not need it
  token = state.push("sup_open", "sup", 1);
  token.markup = "^";
  token = state.push("text", "", 0);
  token.content = content.replace(UNESCAPE_RE, "$1");
  token = state.push("sup_close", "sup", -1);
  token.markup = "^";
  state.pos = state.posMax + 1;
  state.posMax = max;
  return true;
};
const subscript = (state, silent) => {
  let found,
    content,
    token,
    max = state.posMax,
    start = state.pos;
  if (state.src.charCodeAt(start) !== 0x7e /* ~ */) {
    return false;
  }
  if (silent) {
    return false;
  } // don't run any pairs in validation mode
  if (start + 2 >= max) {
    return false;
  }
  state.pos = start + 1;
  while (state.pos < max) {
    if (state.src.charCodeAt(state.pos) === 0x7e /* ~ */) {
      found = true;
      break;
    }
    state.md.inline.skipToken(state);
  }
  if (!found || start + 1 === state.pos) {
    state.pos = start;
    return false;
  }
  content = state.src.slice(start + 1, state.pos);
  // don't allow unescaped spaces/newlines inside
  if (content.match(/(^|[^\\])(\\\\)*\s/)) {
    state.pos = start;
    return false;
  }
  // found!
  state.posMax = state.pos;
  state.pos = start + 1;
  // Earlier we checked !silent, but this implementation does not need it
  token = state.push("sub_open", "sub", 1);
  token.markup = "~";
  token = state.push("text", "", 0);
  token.content = content.replace(UNESCAPE_RE, "$1");
  token = state.push("sub_close", "sub", -1);
  token.markup = "~";
  state.pos = state.posMax + 1;
  state.posMax = max;
  return true;
};
const mermaidMap = {};
const mermaidQuene = new Map();
let mermaidTimeout;
let loadingMermaid = false;
const loadMermaidQuene = () => {
  mermaidQuene.forEach((content, id) => {
    loadSingleMermaid(id, content);
  });
  mermaidQuene.clear();
};
const loadSingleMermaid = async (id, content) => {
  let contianer = document.querySelector("#" + id);
  if (!contianer || contianer.children.length) return;
  let result;
  try {
    let { svg } = await mermaid.render(
      mermaid.detectType(content),
      content,
      contianer
    );
    result = svg;
  } catch (error) {
    return;
  }
  result = result.replace(/(<svg[^>]*?)\sstyle="[^"]*"/i, "$1");
  mermaidMap[id] = result;
  document.querySelectorAll("#" + id).forEach((item) => {
    item.innerHTML = result;
  });
};
const loadRunMermaid = (id, content, bounce = false) => {
  if (typeof mermaid === "undefined") {
    if (!loadingMermaid) {
      loadingMermaid = true;
      let script = document.createElement("script");
      script.crossOrigin = "anonymous";
      script.src =
        "https://fastly.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js";
      script.onload = async () => {
        loadingMermaid = false;
        mermaid.mermaidAPI.initialize({
          startOnLoad: false,
        });
        loadMermaidQuene();
      };
      document.body.appendChild(script);
    }
    if (loading && !bounce) {
      clearTimeout(mermaidTimeout);
      mermaidTimeout = setTimeout(() => {
        mermaidQuene.set(id, content);
        if (typeof mermaid !== "undefined") loadMermaidQuene();
      }, 600);
    } else {
      mermaidQuene.set(id, content);
    }
  } else {
    if (loading && !bounce) {
      clearTimeout(mermaidTimeout);
      mermaidTimeout = setTimeout(() => {
        loadSingleMermaid(id, content);
      }, 600);
    } else {
      setTimeout(() => {
        loadSingleMermaid(id, content);
      });
    }
  }
};
const md = markdownit({
  breaks: true,
  linkify: true,
  highlight: function (str, lang) {
    try {
      return hljs.highlightAuto(str).value;
    } catch (e) {}
    return "";
  },
});
md.inline.ruler.after("emphasis", "sup", superscript);
md.inline.ruler.after("emphasis", "sub", subscript);
md.use(texmath, { engine: katex, delimiters: ["brackets", "dollars"] });
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
  let aIndex = tokens[idx].attrIndex("target");
  if (tokens[idx + 1] && tokens[idx + 1].type === "image")
    tokens[idx].attrPush(["download", ""]);
  else if (aIndex < 0) tokens[idx].attrPush(["target", "_blank"]);
  else tokens[idx].attrs[aIndex][1] = "_blank";
  return self.renderToken(tokens, idx, options);
};
const codeUtils = {
  getCodeLang(str = "") {
    const res = str.match(/ class="language-(.*?)"/);
    return (res && res[1]) || "";
  },
  getFragment(str = "") {
    return str
      ? `<span class="u-mdic-copy-code_lang" text="${str}"></span>`
      : "";
  },
};
const getCodeLangFragment = (oriStr = "") => {
  return codeUtils.getFragment(codeUtils.getCodeLang(oriStr));
};
const copyClickCode = (ele) => {
  const input = document.createElement("textarea");
  input.value = ele.parentElement.nextElementSibling.textContent;
  const nDom = ele.previousElementSibling;
  const nDelay = ele.dataset.mdicNotifyDelay;
  const cDom = nDom.previousElementSibling;
  document.body.appendChild(input);
  input.select();
  input.setSelectionRange(0, input.value.length);
  document.execCommand("copy");
  document.body.removeChild(input);
  if (nDom.style.display === "none") {
    nDom.style.display = "block";
    cDom && (cDom.style.display = "none");
    setTimeout(() => {
      nDom.style.display = "none";
      cDom && (cDom.style.display = "block");
    }, nDelay);
  }
};
const copyClickMd = (idx) => {
  const input = document.createElement("textarea");
  input.value = data[idx].content;
  document.body.appendChild(input);
  input.select();
  input.setSelectionRange(0, input.value.length);
  document.execCommand("copy");
  document.body.removeChild(input);
};
const downloadSVG = (target, ev) => {
  if (ev.target !== target) {
    let blob = new Blob([target.innerHTML], { type: "image/svg+xml" });
    downBlob(
      blob,
      "mermaid-" +
        target.children[0].getAttribute("aria-roledescription") +
        ".svg"
    );
  }
};
const enhanceCode =
  (render, options = {}) =>
  (...args) => {
    const {
      btnText = translations[locale]["copyCode"], // button text
      successText = translations[locale]["copySuccess"], // copy-success text
      successTextDelay = 2000, // successText show time [ms]
      showCodeLanguage = true, // false | show code language
    } = options;
    const [tokens = [], idx = 0] = args;
    const originResult = render.apply(this, args);
    const langFrag = showCodeLanguage ? getCodeLangFragment(originResult) : "";
    const tpls = [
      '<div class="m-mdic-copy-wrapper">',
      `${langFrag}`,
      `<div class="u-mdic-copy-notify" style="display:none;" text="${successText}"></div>`,
      `<button class="u-mdic-copy-btn j-mdic-copy-btn" text="${btnText}" data-mdic-notify-delay="${successTextDelay}" onclick="copyClickCode(this)"></button>`,
      "</div>",
    ];
    if (
      tokens[idx].type === "fence" &&
      langFrag.indexOf(`text="mermaid"`) !== -1
    ) {
      let hash =
        "mermaid" + createHash().update(tokens[idx].content).digest("hex");
      if (mermaidMap[hash])
        return originResult
          .replace("<pre>", `<pre>${tpls.join("")}`)
          .replace(
            "<pre>",
            `<div class="mermaid" id="${hash}" onclick="downloadSVG(this, event)">${mermaidMap[hash]}</div><pre>`
          );
      loadRunMermaid(hash, tokens[idx].content, tokens.length - 1 > idx);
      return originResult
        .replace("<pre>", `<pre>${tpls.join("")}`)
        .replace(
          "<pre>",
          `<div class="mermaid" id="${hash}" onclick="downloadSVG(this, event)"></div><pre>`
        );
    } else {
      return originResult.replace("<pre>", `<pre>${tpls.join("")}`);
    }
  };
md.renderer.rules.code_block = enhanceCode(md.renderer.rules.code_block);
md.renderer.rules.fence = enhanceCode(md.renderer.rules.fence);
md.renderer.rules.image = function (tokens, idx, options, env, self) {
  let token = tokens[idx];
  token.attrs[token.attrIndex("alt")][1] = self.renderInlineAsText(
    token.children,
    options,
    env
  );
  token.attrSet(
    "onload",
    "scrollToBottomLoad(this);this.removeAttribute('onload');this.removeAttribute('onerror')"
  );
  token.attrSet(
    "onerror",
    "scrollToBottomLoad(this);this.removeAttribute('onload');this.removeAttribute('onerror')"
  );
  token.attrPush(["decoding", "async"]);
  token.attrPush(["loading", "lazy"]);
  return self.renderToken(tokens, idx, options);
};
let currentVoiceIdx;
let editingIdx;
let originText;
const resumeSend = () => {
  if (editingIdx !== void 0) {
    chatlog.children[systemRole ? editingIdx - 1 : editingIdx].classList.remove(
      "showEditReq"
    );
  }
  sendBtnEle.children[0].textContent = translations[locale]["send"];
  inputAreaEle.value = originText;
  clearEle.title = translations[locale]["clearChat"];
  clearEle.classList.remove("closeConv");
  originText = void 0;
  editingIdx = void 0;
};
const mdOptionEvent = function (ev) {
  let id = ev.target.dataset.id;
  if (id) {
    let parent = ev.target.parentElement;
    let idxEle = parent.parentElement;
    let idx = Array.prototype.indexOf.call(
      chatlog.children,
      this.parentElement
    );
    if (
      id === "voiceBtn" ||
      id === "speechMd" ||
      id === "pauseMd" ||
      id === "resumeMd"
    ) {
      let classList =
        parent.dataset.id === "voiceBtn"
          ? parent.classList
          : ev.target.classList;
      if (classList.contains("readyVoice")) {
        if (chatlog.children[idx].dataset.loading !== "true") {
          idx = systemRole ? idx + 1 : idx;
          speechEvent(idx);
        }
      } else if (classList.contains("pauseVoice")) {
        if (voiceIns) {
          if (voiceIns instanceof Audio) voiceIns.pause();
          else {
            if (supportSpe) speechSynthesis.pause();
            classList.remove("readyVoice");
            classList.remove("pauseVoice");
            classList.add("resumeVoice");
          }
        }
      } else {
        if (voiceIns) {
          if (voiceIns instanceof Audio) voiceIns.play();
          else {
            if (supportSpe) speechSynthesis.resume();
            classList.remove("readyVoice");
            classList.remove("resumeVoice");
            classList.add("pauseVoice");
          }
        }
      }
    } else if (id === "editMd") {
      let reqEle = chatlog.children[idx];
      idx = systemRole ? idx + 1 : idx;
      if (editingIdx === idx) return;
      if (editingIdx !== void 0) {
        chatListEle.children[
          systemRole ? editingIdx - 1 : editingIdx
        ].classList.remove("showEditReq");
      }
      reqEle.classList.add("showEditReq");
      editingIdx = idx;
      originText = inputAreaEle.value;
      inputAreaEle.value = data[idx].content;
      inputAreaEle.dispatchEvent(new Event("input"));
      inputAreaEle.focus();
      sendBtnEle.children[0].textContent = translations[locale]["update"];
      clearEle.title = translations[locale]["cancel"];
      clearEle.classList.add("closeConv");
    } else if (id === "refreshMd") {
      if (noLoading()) {
        formatAvatarEle(chatlog.children[idx].children[0], modelVersion);
        if (ev.target.classList.contains("refreshReq")) {
          chatlog.children[idx].children[1].innerHTML =
            "<p class='cursorCls'><br /></p>";
          chatlog.children[idx].dataset.loading = true;
          idx = systemRole ? idx + 1 : idx;
          data[idx].content = "";
          if (
            idx ===
            data.findIndex((item) => {
              return item.role === "assistant";
            })
          )
            activeChatEle.children[1].children[1].textContent = "";
          if (idx === currentVoiceIdx) endSpeak();
          loadAction(true);
          refreshIdx = idx;
          streamGen();
        } else {
          chatlog.children[idx].dataset.loading = true;
          idx = systemRole ? idx + 1 : idx;
          progressData = data[idx].content;
          loadAction(true);
          refreshIdx = idx;
          streamGen(true);
        }
      }
    } else if (id === "copyMd") {
      idx = systemRole ? idx + 1 : idx;
      copyClickMd(idx);
      notyf.success(translations[locale]["copySuccess"]);
    } else if (id === "delMd") {
      if (noLoading()) {
        if (confirmAction(translations[locale]["delMsgTip"])) {
          chatlog.removeChild(chatlog.children[idx]);
          idx = systemRole ? idx + 1 : idx;
          let firstIdx = data.findIndex((item) => {
            return item.role === "assistant";
          });
          if (currentVoiceIdx !== void 0) {
            if (currentVoiceIdx === idx) {
              endSpeak();
            } else if (currentVoiceIdx > idx) {
              currentVoiceIdx--;
            }
          }
          if (editingIdx !== void 0) {
            if (editingIdx === idx) {
              resumeSend();
            } else if (editingIdx > idx) {
              editingIdx--;
            }
          }
          data.splice(idx, 1);
          if (firstIdx === idx) updateChatPre();
          updateChats();
        }
      }
    } else if (id === "downAudioMd") {
      if (chatlog.children[idx].dataset.loading !== "true") {
        idx = systemRole ? idx + 1 : idx;
        downloadAudio(idx);
      }
    }
  }
};
const formatAvatarEle = (ele, model) => {
  ele.className = "chatAvatar";
  if (ele.parentElement.className === "request") {
    ele.innerHTML = `<img src="${userAvatar}" />`;
  } else {
    if (model.startsWith("gpt")) {
      ele.classList.add("gptAvatar");
      ele.innerHTML = `<svg width="24" height="24"><use xlink:href="#aiIcon"></use></svg>`;
    } else if (model.startsWith("gemini")) {
      ele.innerHTML = `<svg width="30" height="30"><use xlink:href="#geminiIcon"></use></svg>`;
    } else {
      ele.innerHTML = `<svg width="30" height="30"><use xlink:href="#claudeIcon"></use></svg>`;
    }
  }
};
const formatMdEle = (ele, model) => {
  let avatar = document.createElement("div");
  ele.appendChild(avatar);
  formatAvatarEle(avatar, model);
  let realMd = document.createElement("div");
  realMd.className =
    ele.className === "request" ? "requestBody" : "markdown-body";
  ele.appendChild(realMd);
  let mdOption = document.createElement("div");
  mdOption.className = "mdOption";
  ele.appendChild(mdOption);
  let optionWidth = existVoice >= 2 ? 140 : 105;
  mdOption.innerHTML +=
    `<div class="optionItems" style="width:${optionWidth}px;left:-${
      optionWidth - 10
    }px">` +
    (ele.className === "request"
      ? `<div data-id="editMd" class="optionItem" title="${translations[locale]["edit"]}">
                <svg width="18" height="18"><use xlink:href="#chatEditIcon" /></svg>
                </div>`
      : `<div data-id="refreshMd" class="refreshReq optionItem" title="${translations[locale]["refresh"]}">
                <svg width="18" height="18" ><use xlink:href="#refreshIcon" /></svg>
                <svg width="18" height="18" ><use xlink:href="#halfRefIcon" /></svg>
                </div>`) +
    `<div data-id="copyMd" class="optionItem" title="${translations[locale]["copy"]}">
                <svg width="20" height="20"><use xlink:href="#copyIcon" /></svg>
            </div>
            <div data-id="delMd" class="optionItem" title="${translations[locale]["del"]}">
                <svg width="20" height="20"><use xlink:href="#delIcon" /></svg>
            </div>` +
    (existVoice >= 2
      ? `<div data-id="downAudioMd" class="optionItem" title="${translations[locale]["downAudio"]}">
                <svg width="20" height="20"><use xlink:href="#downAudioIcon" /></svg>
            </div>`
      : "") +
    `</div>`;
  if (existVoice) {
    mdOption.innerHTML += `<div class="voiceCls readyVoice" data-id="voiceBtn">
                <svg width="20" height="20" role="img" data-id="speechMd"><title>${translations[locale]["speech"]}</title><use xlink:href="#readyVoiceIcon" /></svg>
                <svg width="20" height="20" role="img" data-id="pauseMd"><title>${translations[locale]["pause"]}</title><use xlink:href="#pauseVoiceIcon" /></svg>
                <svg width="20" height="20" role="img" data-id="resumeMd"><title>${translations[locale]["resume"]}</title><use xlink:href="#resumeVoiceIcon" /></svg>
                </div>`;
  }
  mdOption.onclick = mdOptionEvent;
};
let allListEle = chatListEle.parentElement;
let folderData = [];
let chatsData = [];
let chatIdxs = [];
let searchIdxs = [];
let activeChatIdx = 0;
let activeChatEle;
let operateChatIdx, operateFolderIdx;
let dragLi, dragType, dragIdx;
let mobileDragOut;
const mobileDragStartEV = function (ev) {
  if (mobileDragOut !== void 0) {
    clearTimeout(mobileDragOut);
    mobileDragOut = void 0;
  }
  mobileDragOut = setTimeout(() => {
    this.setAttribute("draggable", "true");
    this.dispatchEvent(ev);
  }, 200);
};
if (isMobile) {
  let stopDragOut = () => {
    if (mobileDragOut !== void 0) {
      clearTimeout(mobileDragOut);
      mobileDragOut = void 0;
    }
  };
  let stopDrag = () => {
    stopDragOut();
    document.querySelectorAll("[draggable=true]").forEach((ele) => {
      ele.setAttribute("draggable", "false");
    });
  };
  document.body.addEventListener("touchmove", stopDragOut);
  document.body.addEventListener("touchend", stopDrag);
  document.body.addEventListener("touchcancel", stopDrag);
}
const delDragIdx = () => {
  let chatIdx = chatIdxs.indexOf(dragIdx);
  if (chatIdx !== -1) {
    chatIdxs.splice(chatIdx, 1);
  } else {
    folderData.forEach((item, i) => {
      let inIdx = item.idxs.indexOf(dragIdx);
      if (inIdx !== -1) {
        item.idxs.splice(inIdx, 1);
        updateFolder(i);
      }
    });
  }
};
const updateFolder = (idx) => {
  let folderEle = folderListEle.children[idx];
  let childLen = folderData[idx].idxs.length;
  folderEle.children[0].children[1].children[1].textContent =
    childLen + translations[locale]["chats"];
  folderEle.classList.toggle("expandFolder", childLen);
};
folderListEle.ondragenter = chatListEle.ondragenter = function (ev) {
  ev.preventDefault();
  if (ev.target === dragLi) return;
  allListEle.querySelectorAll(".dragingChat").forEach((ele) => {
    ele.classList.remove("dragingChat");
  });
  if (dragType === "chat") {
    if (this === chatListEle) {
      this.classList.add("dragingChat");
      let dragindex = Array.prototype.indexOf.call(
        chatListEle.children,
        dragLi
      );
      let targetindex = Array.prototype.indexOf.call(
        chatListEle.children,
        ev.target
      );
      delDragIdx();
      if (targetindex !== -1) {
        chatIdxs.splice(targetindex, 0, dragIdx);
        if (dragindex === -1 || dragindex >= targetindex) {
          chatListEle.insertBefore(dragLi, ev.target);
        } else {
          chatListEle.insertBefore(dragLi, ev.target.nextElementSibling);
        }
      } else {
        chatIdxs.push(dragIdx);
        chatListEle.appendChild(dragLi);
      }
    } else if (this === folderListEle) {
      let folderIdx;
      if (ev.target.classList.contains("headLi")) {
        ev.target.parentElement.classList.add("dragingChat");
        ev.target.nextElementSibling.appendChild(dragLi);
        delDragIdx();
        folderIdx = Array.prototype.indexOf.call(
          folderListEle.children,
          ev.target.parentElement
        );
        folderData[folderIdx].idxs.push(dragIdx);
        updateFolder(folderIdx);
      } else if (ev.target.classList.contains("chatLi")) {
        ev.target.parentElement.parentElement.classList.add("dragingChat");
        let parent = ev.target.parentElement;
        delDragIdx();
        folderIdx = Array.prototype.indexOf.call(
          folderListEle.children,
          parent.parentElement
        );
        let dragindex = Array.prototype.indexOf.call(parent.children, dragLi);
        let targetindex = Array.prototype.indexOf.call(
          parent.children,
          ev.target
        );
        if (dragindex !== -1) {
          folderData[folderIdx].idxs.splice(targetindex, 0, dragIdx);
          if (dragindex < targetindex) {
            parent.insertBefore(dragLi, ev.target.nextElementSibling);
          } else {
            parent.insertBefore(dragLi, ev.target);
          }
        } else {
          folderData[folderIdx].idxs.push(dragIdx);
          parent.appendChild(dragLi);
        }
        updateFolder(folderIdx);
      }
    }
    updateChatIdxs();
  } else if (dragType === "folder") {
    if (this === folderListEle) {
      let dragindex = Array.prototype.indexOf.call(
        folderListEle.children,
        dragLi
      );
      let folderIdx = Array.prototype.findIndex.call(
        folderListEle.children,
        (item) => {
          return item.contains(ev.target);
        }
      );
      folderListEle.children[folderIdx].classList.remove("expandFolder");
      let folderEle = folderListEle.children[folderIdx];
      let data = folderData.splice(dragindex, 1)[0];
      folderData.splice(folderIdx, 0, data);
      if (dragindex === -1 || dragindex >= folderIdx) {
        folderListEle.insertBefore(dragLi, folderEle);
      } else {
        folderListEle.insertBefore(dragLi, folderEle.nextElementSibling);
      }
      updateChatIdxs();
    }
  }
};
folderListEle.ondragover = chatListEle.ondragover = (ev) => {
  ev.preventDefault();
};
folderListEle.ondragend = chatListEle.ondragend = (ev) => {
  document.getElementsByClassName("dragingLi")[0].classList.remove("dragingLi");
  allListEle.querySelectorAll(".dragingChat").forEach((ele) => {
    ele.classList.remove("dragingChat");
  });
  dragType = dragIdx = dragLi = void 0;
};
const chatDragStartEv = function (ev) {
  ev.stopPropagation();
  dragLi = this;
  dragLi.classList.add("dragingLi");
  dragType = "chat";
  if (chatListEle.contains(this)) {
    let idx = Array.prototype.indexOf.call(chatListEle.children, this);
    dragIdx = chatIdxs[idx];
  } else if (folderListEle.contains(this)) {
    let folderIdx = Array.prototype.indexOf.call(
      folderListEle.children,
      this.parentElement.parentElement
    );
    let inFolderIdx = Array.prototype.indexOf.call(
      this.parentElement.children,
      this
    );
    dragIdx = folderData[folderIdx].idxs[inFolderIdx];
  }
};
const extraFolderActive = (folderIdx) => {
  let folderNewIdx = -1;
  for (let i = folderIdx - 1; i >= 0; i--) {
    if (folderData[i].idxs.length) {
      folderNewIdx = i;
    }
  }
  if (folderNewIdx === -1) {
    for (let i = folderIdx + 1; i < folderData.length; i++) {
      if (folderData[i].idxs.length) folderNewIdx = i;
    }
  }
  if (folderNewIdx !== -1) {
    activeChatIdx = folderData[folderNewIdx].idxs[0];
  } else if (chatIdxs.length) {
    activeChatIdx = chatIdxs[0];
  } else {
    activeChatIdx = -1;
  }
};
const delFolder = (folderIdx, ele) => {
  if (confirmAction(translations[locale]["delFolderTip"])) {
    let delData = folderData[folderIdx];
    let idxs = delData.idxs.sort();
    ele.parentElement.remove();
    if (idxs.indexOf(activeChatIdx) !== -1) {
      endAll();
      extraFolderActive(folderIdx);
    }
    folderData.splice(folderIdx, 1);
    for (let i = idxs.length - 1; i >= 0; i--) {
      chatsData.splice(idxs[i], 1);
    }
    folderData.forEach((item) => {
      if (item.idxs.length) {
        item.idxs.forEach((i, ix) => {
          let len = idxs.filter((j) => {
            return i > j;
          }).length;
          if (len) {
            item.idxs[ix] = i - len;
          }
        });
      }
    });
    chatIdxs.forEach((item, ix) => {
      let len = idxs.filter((j) => {
        return item > j;
      }).length;
      if (len) chatIdxs[ix] = item - len;
    });
    let len = idxs.filter((j) => {
      return activeChatIdx > j;
    }).length;
    if (len) activeChatIdx -= len;
    if (activeChatIdx === -1) {
      addNewChat();
      activeChatIdx = 0;
      chatEleAdd(activeChatIdx);
    }
    updateChats();
    activeChat();
  }
};
const folderAddChat = (folderIdx, headEle) => {
  endAll();
  let chat = { name: translations[locale]["newChatName"], data: [] };
  chatsData.push(chat);
  activeChatIdx = chatsData.length - 1;
  folderData[folderIdx].idxs.push(activeChatIdx);
  let ele = chatEleAdd(activeChatIdx, false);
  headEle.nextElementSibling.appendChild(ele);
  updateFolder(folderIdx);
  updateChats();
  activeChat(ele);
};
const folderEleEvent = function (ev) {
  ev.preventDefault();
  ev.stopPropagation();
  let parent = this.parentElement;
  let idx = Array.prototype.indexOf.call(folderListEle.children, parent);
  if (ev.target.className === "headLi") {
    let isExpanded = parent.classList.toggle("expandFolder");
    if (folderData[idx].idxs.indexOf(activeChatIdx) !== -1) {
      parent.classList.toggle("activeFolder", !isExpanded);
    }
  } else if (ev.target.dataset.type === "folderAddChat") {
    folderAddChat(idx, this);
  } else if (ev.target.dataset.type === "folderEdit") {
    toEditName(idx, this, 0);
  } else if (ev.target.dataset.type === "folderDel") {
    delFolder(idx, this);
  }
};
const folderDragStartEv = function (ev) {
  dragLi = this;
  dragLi.classList.add("dragingLi");
  dragType = "folder";
  dragIdx = Array.prototype.indexOf.call(folderListEle.children, this);
};
const folderEleAdd = (idx, push = true) => {
  let folder = folderData[idx];
  let folderEle = document.createElement("div");
  folderEle.className = "folderLi";
  if (!isMobile) folderEle.setAttribute("draggable", "true");
  else folderEle.ontouchstart = mobileDragStartEV;
  let headEle = document.createElement("div");
  headEle.className = "headLi";
  headEle.innerHTML = `<svg width="24" height="24"><use xlink:href="#expandFolderIcon" /></svg>
                <div class="folderInfo">
                    <div class="folderName"></div>
                    <div class="folderNum"></div>
                </div>
                <div class="folderOption">
                    <svg data-type="folderAddChat" width="24" height="24" role="img"><title>${translations[locale]["newChat"]}</title><use xlink:href="#addIcon" /></svg>
                    <svg data-type="folderEdit" width="24" height="24" role="img"><title>${translations[locale]["edit"]}</title><use xlink:href="#chatEditIcon" /></svg>
                    <svg data-type="folderDel" width="24" height="24" role="img"><title>${translations[locale]["del"]}</title><use xlink:href="#delIcon" /></svg>
                </div>`;
  headEle.children[1].children[0].textContent = folder.name;
  headEle.children[1].children[1].textContent =
    folder.idxs.length + translations[locale]["chats"];
  folderEle.appendChild(headEle);
  folderEle.ondragstart = folderDragStartEv;
  headEle.onclick = folderEleEvent;
  let chatsEle = document.createElement("div");
  chatsEle.className = "chatsInFolder";
  for (let i = 0; i < folder.idxs.length; i++) {
    chatsEle.appendChild(chatEleAdd(folder.idxs[i], false));
  }
  folderEle.appendChild(chatsEle);
  if (push) {
    folderListEle.appendChild(folderEle);
  } else {
    folderListEle.insertBefore(folderEle, folderListEle.firstChild);
  }
};
document.getElementById("newFolder").onclick = function () {
  folderData.unshift({
    name: translations[locale]["newFolderName"],
    idxs: [],
  });
  folderEleAdd(0, false);
  updateChatIdxs();
  folderListEle.parentElement.scrollTop = 0;
};
const initChatEle = (index, chatEle) => {
  chatEle.children[1].children[0].textContent = chatsData[index].name;
  let chatPreview = "";
  if (chatsData[index].data && chatsData[index].data.length) {
    let first = chatsData[index].data.find((item) => {
      return item.role === "assistant";
    });
    if (first) {
      chatPreview = first.content.slice(0, 30);
    }
  }
  chatEle.children[1].children[1].textContent = chatPreview;
};
const chatEleAdd = (idx, appendChat = true) => {
  let chat = chatsData[idx];
  let chatEle = document.createElement("div");
  chatEle.className = "chatLi";
  if (!isMobile) chatEle.setAttribute("draggable", "true");
  else chatEle.ontouchstart = mobileDragStartEV;
  chatEle.ondragstart = chatDragStartEv;
  chatEle.innerHTML = `<svg width="24" height="24"><use xlink:href="#chatIcon" /></svg>
                <div class="chatInfo">
                    <div class="chatName"></div>
                    <div class="chatPre"></div>
                </div>
                <div class="chatOption"><svg data-type="chatEdit" width="24" height="24" role="img"><title>${translations[locale]["edit"]}</title><use xlink:href="#chatEditIcon" /></svg>
                <svg data-type="chatDel" width="24" height="24" role="img"><title>${translations[locale]["del"]}</title><use xlink:href="#delIcon" /></svg></div>`;
  if (appendChat) chatListEle.appendChild(chatEle);
  initChatEle(idx, chatEle);
  chatEle.onclick = chatEleEvent;
  return chatEle;
};
const addNewChat = () => {
  let chat = { name: translations[locale]["newChatName"], data: [] };
  if (presetRoleData.default)
    chat.data.unshift({
      role: "system",
      content: presetRoleData.default,
    });
  preEle.selectedIndex = 0;
  chatsData.push(chat);
  chatIdxs.push(chatsData.length - 1);
  updateChats();
};
const delChat = (idx, ele, folderIdx, inFolderIdx) => {
  if (confirmAction(translations[locale]["delChatTip"])) {
    if (idx === activeChatIdx) endAll();
    if (folderIdx !== void 0) {
      let folder = folderData[folderIdx];
      folder.idxs.splice(inFolderIdx, 1);
      updateFolder(folderIdx);
      if (idx === activeChatIdx) {
        if (inFolderIdx - 1 >= 0) {
          activeChatIdx = folder.idxs[inFolderIdx - 1];
        } else if (folder.idxs.length) {
          activeChatIdx = folder.idxs[0];
        } else {
          extraFolderActive(folderIdx);
        }
      }
    } else {
      let chatIdx = chatIdxs.indexOf(idx);
      chatIdxs.splice(chatIdx, 1);
      if (idx === activeChatIdx) {
        if (chatIdx - 1 >= 0) {
          activeChatIdx = chatIdxs[chatIdx - 1];
        } else if (chatIdxs.length) {
          activeChatIdx = chatIdxs[0];
        } else {
          let folderNewIdx = -1;
          for (let i = folderData.length - 1; i >= 0; i--) {
            if (folderData[i].idxs.length) folderNewIdx = i;
          }
          if (folderNewIdx !== -1) {
            activeChatIdx = folderData[folderNewIdx].idxs[0];
          } else {
            activeChatIdx = -1;
          }
        }
      }
    }
    if (activeChatIdx > idx) activeChatIdx--;
    chatsData.splice(idx, 1);
    ele.remove();
    folderData.forEach((item) => {
      if (item.idxs.length) {
        item.idxs.forEach((i, ix) => {
          if (i > idx) item.idxs[ix] = i - 1;
        });
      }
    });
    chatIdxs.forEach((item, ix) => {
      if (item > idx) chatIdxs[ix] = item - 1;
    });
    if (activeChatIdx === -1) {
      addNewChat();
      activeChatIdx = 0;
      chatEleAdd(activeChatIdx);
    }
    updateChats();
    activeChat();
  }
};
const endEditEvent = (ev) => {
  if (!document.getElementById("activeChatEdit").contains(ev.target)) {
    endEditChat();
  }
};
const preventDrag = (ev) => {
  ev.preventDefault();
  ev.stopPropagation();
};
const endEditChat = () => {
  if (operateChatIdx !== void 0) {
    let ele = getChatEle(operateChatIdx);
    chatsData[operateChatIdx].name = ele.children[1].children[0].textContent =
      document.getElementById("activeChatEdit").value;
    ele.lastElementChild.remove();
  } else if (operateFolderIdx !== void 0) {
    let ele = folderListEle.children[operateFolderIdx].children[0];
    folderData[operateFolderIdx].name =
      ele.children[1].children[0].textContent =
        document.getElementById("activeChatEdit").value;
    ele.lastElementChild.remove();
  }
  updateChats();
  operateChatIdx = operateFolderIdx = void 0;
  document.body.removeEventListener("mousedown", endEditEvent, true);
};
const toEditName = (idx, ele, type) => {
  let inputEle = document.createElement("input");
  inputEle.id = "activeChatEdit";
  inputEle.setAttribute("draggable", "true");
  inputEle.ondragstart = preventDrag;
  ele.appendChild(inputEle);
  if (type) {
    inputEle.value = chatsData[idx].name;
    operateChatIdx = idx;
  } else {
    inputEle.value = folderData[idx].name;
    operateFolderIdx = idx;
  }
  inputEle.setSelectionRange(0, 0);
  inputEle.focus();
  inputEle.onkeydown = (e) => {
    if (e.keyCode === 13) {
      e.preventDefault();
      endEditChat();
    }
  };
  document.body.addEventListener("mousedown", endEditEvent, true);
  return inputEle;
};
const chatEleEvent = function (ev) {
  ev.preventDefault();
  ev.stopPropagation();
  let idx, folderIdx, inFolderIdx;
  if (chatListEle.contains(this)) {
    idx = Array.prototype.indexOf.call(chatListEle.children, this);
    idx = chatIdxs[idx];
  } else if (folderListEle.contains(this)) {
    folderIdx = Array.prototype.indexOf.call(
      folderListEle.children,
      this.parentElement.parentElement
    );
    inFolderIdx = Array.prototype.indexOf.call(
      this.parentElement.children,
      this
    );
    idx = folderData[folderIdx].idxs[inFolderIdx];
  }
  if (ev.target.classList.contains("chatLi")) {
    if (searchChatEle.value || activeChatIdx !== idx) {
      endAll();
      activeChatIdx = idx;
      activeChat(this);
    }
    if (window.innerWidth <= 800) {
      document.body.classList.remove("show-nav");
    }
  } else if (ev.target.dataset.type === "chatEdit") {
    toEditName(idx, this, 1);
  } else if (ev.target.dataset.type === "chatDel") {
    delChat(idx, this, folderIdx, inFolderIdx);
  }
};
const updateChats = () => {
  localStorage.setItem("chats", JSON.stringify(chatsData));
  updateChatIdxs();
};
const updateChatIdxs = () => {
  localStorage.setItem("chatIdxs", JSON.stringify(chatIdxs));
  localStorage.setItem("folders", JSON.stringify(folderData));
};
const createConvEle = (className, append = true, model) => {
  let div = document.createElement("div");
  div.className = className;
  formatMdEle(div, model);
  if (append) chatlog.appendChild(div);
  return div;
};
const getChatEle = (idx) => {
  let chatIdx = chatIdxs.indexOf(idx);
  if (chatIdx !== -1) {
    return chatListEle.children[chatIdx];
  } else {
    let inFolderIdx;
    let folderIdx = folderData.findIndex((item) => {
      inFolderIdx = item.idxs.indexOf(idx);
      return inFolderIdx !== -1;
    });
    if (folderIdx !== -1) {
      return folderListEle.children[folderIdx].children[1].children[
        inFolderIdx
      ];
    }
  }
};
const activeChat = (ele) => {
  data = chatsData[activeChatIdx]["data"];
  allListEle.querySelectorAll(".activeChatLi").forEach((ele) => {
    ele.classList.remove("activeChatLi");
  });
  allListEle.querySelectorAll(".activeFolder").forEach((ele) => {
    ele.classList.remove("activeFolder");
  });
  if (!ele) ele = getChatEle(activeChatIdx);
  ele.classList.add("activeChatLi");
  activeChatEle = ele;
  if (chatIdxs.indexOf(activeChatIdx) === -1) {
    if (!ele.parentElement.parentElement.classList.contains("expandFolder")) {
      ele.parentElement.parentElement.classList.add("activeFolder");
    }
  }
  if (data[0] && data[0].role === "system") {
    systemRole = data[0].content;
    systemEle.value = systemRole;
  } else {
    systemRole = void 0;
    systemEle.value = "";
  }
  chatlog.innerHTML = "";
  if (systemRole ? data.length - 1 : data.length) {
    let firstIdx = systemRole ? 1 : 0;
    for (let i = firstIdx; i < data.length; i++) {
      if (data[i].role === "user") {
        createConvEle("request").children[1].textContent = data[i].content;
      } else {
        createConvEle("response", true, data[i].model).children[1].innerHTML =
          md.render(data[i].content) || "<br />";
      }
    }
  }
  let top = ele.offsetTop + ele.offsetHeight - allListEle.clientHeight;
  if (allListEle.scrollTop < top) allListEle.scrollTop = top;
  localStorage.setItem("activeChatIdx", activeChatIdx);
  if (searchIdxs[activeChatIdx] !== void 0) {
    let dataIdx = searchIdxs[activeChatIdx];
    if (dataIdx !== -1) {
      let currChatEle = chatlog.children[systemRole ? dataIdx - 1 : dataIdx];
      let childs = currChatEle.children[1].getElementsByTagName("*");
      if (childs.length) {
        for (let i = childs.length - 1; i >= 0; i--) {
          if (
            childs[i].textContent &&
            childs[i].textContent.indexOf(searchChatEle.value) !== -1
          ) {
            let offTop = findOffsetTop(childs[i], messagesEle);
            messagesEle.scrollTop =
              offTop + childs[i].offsetHeight - messagesEle.clientHeight * 0.15;
            break;
          }
        }
      } else messagesEle.scrollTop = currChatEle.offsetTop;
    } else messagesEle.scrollTop = 0;
  }
};
newChatEle.onclick = () => {
  endAll();
  addNewChat();
  activeChatIdx = chatsData.length - 1;
  chatEleAdd(activeChatIdx);
  activeChat(chatListEle.lastElementChild);
};
const initChats = () => {
  let localChats = localStorage.getItem("chats");
  let localFolders = localStorage.getItem("folders");
  let localChatIdxs = localStorage.getItem("chatIdxs");
  let localChatIdx = localStorage.getItem("activeChatIdx");
  activeChatIdx = (localChatIdx && parseInt(localChatIdx)) || 0;
  if (localChats) {
    if (isCompressedChats)
      localChats = new TextDecoder().decode(
        inflateSync(stringToUint(localChats))
      );
    chatsData = JSON.parse(localChats);
    let folderIdxs = [];
    if (localFolders) {
      folderData = JSON.parse(localFolders);
      for (let i = 0; i < folderData.length; i++) {
        folderEleAdd(i);
        folderIdxs.push(...folderData[i].idxs);
      }
    }
    if (localChatIdxs) {
      chatIdxs = JSON.parse(localChatIdxs);
      for (let i = 0; i < chatIdxs.length; i++) {
        chatEleAdd(chatIdxs[i]);
      }
    } else {
      for (let i = 0; i < chatsData.length; i++) {
        if (folderIdxs.indexOf(i) === -1) {
          chatIdxs.push(i);
          chatEleAdd(i);
        }
      }
      updateChatIdxs();
    }
  } else {
    addNewChat();
    chatEleAdd(activeChatIdx);
  }
};
const initExpanded = () => {
  let folderIdx = folderData.findIndex((item) => {
    return item.idxs.indexOf(activeChatIdx) !== -1;
  });
  if (folderIdx !== -1) {
    folderListEle.children[folderIdx].classList.add("expandFolder");
  }
};
initChats();
initExpanded();
activeChat();
document.getElementById("clearSearch").onclick = () => {
  searchChatEle.value = "";
  searchChatEle.dispatchEvent(new Event("input"));
  searchChatEle.focus();
};
const toSearchChats = () => {
  searchIdxs.length = 0;
  for (let i = 0; i < chatsData.length; i++) {
    let chatEle = getChatEle(i);
    chatEle.style.display = null;
    let flags = isCaseSearch ? "" : "i";
    let pattern = escapeRegexExp(searchChatEle.value);
    let regex = new RegExp(pattern, flags);
    let nameData = chatsData[i].name.match(regex);
    let nameIdx = nameData ? nameData.index : -1;
    let matchContent;
    let dataIdx = chatsData[i].data.findIndex((item) => {
      return (
        item.role !== "system" && (matchContent = item.content.match(regex))
      );
    });
    if (nameIdx !== -1 || dataIdx !== -1) {
      let ele = chatEle.children[1];
      if (dataIdx !== -1) {
        let data = chatsData[i].data[dataIdx];
        let idx = matchContent.index;
        let endIdx = idx + matchContent[0].length;
        ele.children[1].textContent =
          (idx > 8 ? "..." : "") +
          data.content.slice(idx > 8 ? idx - 5 : 0, idx);
        ele.children[1].appendChild(document.createElement("span"));
        ele.children[1].lastChild.textContent = data.content.slice(idx, endIdx);
        ele.children[1].appendChild(
          document.createTextNode(data.content.slice(endIdx))
        );
      } else {
        initChatEle(i, chatEle);
      }
      if (nameIdx !== -1) {
        let endIdx = nameIdx + nameData[0].length;
        ele.children[0].textContent =
          (nameIdx > 5 ? "..." : "") +
          chatsData[i].name.slice(nameIdx > 5 ? nameIdx - 3 : 0, nameIdx);
        ele.children[0].appendChild(document.createElement("span"));
        ele.children[0].lastChild.textContent = chatsData[i].name.slice(
          nameIdx,
          endIdx
        );
        ele.children[0].appendChild(
          document.createTextNode(chatsData[i].name.slice(endIdx))
        );
      } else {
        ele.children[0].textContent = chatsData[i].name;
      }
      searchIdxs[i] = dataIdx;
    } else {
      chatEle.style.display = "none";
      initChatEle(i, chatEle);
    }
  }
  for (let i = 0; i < folderListEle.children.length; i++) {
    let folderChatEle = folderListEle.children[i].children[1];
    if (
      !folderChatEle.children.length ||
      Array.prototype.filter.call(folderChatEle.children, (ele) => {
        return ele.style.display !== "none";
      }).length === 0
    ) {
      folderListEle.children[i].style.display = "none";
    }
  }
};
searchChatEle.oninput = (ev) => {
  if (searchChatEle.value.length) {
    toSearchChats();
  } else {
    searchIdxs.length = 0;
    for (let i = 0; i < chatsData.length; i++) {
      let chatEle = getChatEle(i);
      chatEle.style.display = null;
      initChatEle(i, chatEle);
    }
    for (let i = 0; i < folderListEle.children.length; i++) {
      folderListEle.children[i].style.display = null;
    }
  }
};
document.getElementById("resetHotKey").onclick = () => {
  localStorage.removeItem("hotKeys");
  initHotKey();
  notyf.success(translations[locale]["resetSetSuccTip"]);
};
const blobToText = (blob) => {
  return new Promise((res, rej) => {
    let reader = new FileReader();
    reader.readAsText(blob);
    reader.onload = () => {
      res(reader.result);
    };
    reader.onerror = (error) => {
      rej(error);
    };
  });
};
document.getElementById("exportChat").onclick = () => {
  if (loading) stopLoading();
  let data = {
    chatsData: chatsData,
    folderData: folderData,
    chatIdxs: chatIdxs,
  };
  let blob = new Blob([JSON.stringify(data, null, 2)], {
    type: "application/json",
  });
  let date = new Date();
  let fileName =
    "chats-" +
    date.getFullYear() +
    "-" +
    (date.getMonth() + 1) +
    "-" +
    date.getDate() +
    ".json";
  downBlob(blob, fileName);
  notyf.success(translations[locale]["exportSuccTip"]);
};
document.getElementById("importChatInput").onchange = function () {
  let file = this.files[0];
  blobToText(file).then((text) => {
    try {
      let json = JSON.parse(text);
      let checked =
        json.chatsData &&
        json.folderData &&
        json.chatIdxs &&
        json.chatsData.every((item) => {
          return item.name !== void 0 && item.data !== void 0;
        });
      if (checked) {
        let preFolder = folderData.length;
        let preLen = chatsData.length;
        if (json.chatsData) {
          chatsData = chatsData.concat(json.chatsData);
        }
        if (json.folderData) {
          for (let i = 0; i < json.folderData.length; i++) {
            json.folderData[i].idxs = json.folderData[i].idxs.map((item) => {
              return item + preLen;
            });
            folderData.push(json.folderData[i]);
            folderEleAdd(i + preFolder);
          }
        }
        if (json.chatIdxs) {
          for (let i = 0; i < json.chatIdxs.length; i++) {
            let newIdx = json.chatIdxs[i] + preLen;
            chatIdxs.push(newIdx);
            chatEleAdd(newIdx);
          }
        }
        updateChats();
        checkStorage();
        notyf.success(translations[locale]["importSuccTip"]);
      } else {
        throw new Error("fmt error");
      }
    } catch (e) {
      notyf.error(translations[locale]["importFailTip"]);
    }
    this.value = "";
  });
};
clearChatSet.onclick = clearChat.onclick = () => {
  if (confirmAction(translations[locale]["clearAllTip"])) {
    chatsData.length = 0;
    chatIdxs.length = 0;
    folderData.length = 0;
    folderListEle.innerHTML = "";
    chatListEle.innerHTML = "";
    endAll();
    addNewChat();
    activeChatIdx = 0;
    chatEleAdd(activeChatIdx);
    localStorage.removeItem("compressedChats");
    isCompressedChats = false;
    updateChats();
    checkStorage();
    activeChat(chatListEle.firstElementChild);
    notyf.success(translations[locale]["clearChatSuccTip"]);
  }
};
let localSetKeys = [
  "modelVersion",
  "APISelect",
  "GeminiAPISelect",
  "ClaudeAPISelect",
  "APIHost",
  "GeminiAPIHost",
  "ClaudeAPIHost",
  "APIKey",
  "GeminiAPIKey",
  "ClaudeAPIKey",
  "APIModel",
  "GeminiAPIModel",
  "ClaudeAPIModel",
  "hotKeys",
  "userAvatar",
  "system",
  "temp",
  "top_p",
  "convWidth0",
  "convWidth1",
  "textSpeed",
  "contLen",
  "enableLongReply",
  "existVoice",
  "voiceTestText",
  "azureRegion",
  "azureKey",
  "enableContVoice",
  "enableAutoVoice",
  "existRec",
  "azureRecRegion",
  "azureRecKey",
  "voiceRecLang",
  "autoVoiceSendWord",
  "autoVoiceStopWord",
  "autoVoiceSendOut",
  "keepListenMic",
  "fullWindow",
  "themeMode",
  "autoThemeMode",
  "customDarkTime",
  "UILang",
  "pinNav",
  "voice0",
  "voicePitch0",
  "voiceVolume0",
  "voiceRate0",
  "azureRole0",
  "azureStyle0",
  "voice1",
  "voicePitch1",
  "voiceVolume1",
  "voiceRate1",
  "azureRole1",
  "azureStyle1",
  "searchFlag",
];
document.getElementById("exportSet").onclick = () => {
  let data = {};
  for (let i = 0; i < localSetKeys.length; i++) {
    let key = localSetKeys[i];
    let val = localStorage.getItem(key);
    if (val != void 0) data[key] = val;
  }
  let blob = new Blob([JSON.stringify(data, null, 2)], {
    type: "application/json",
  });
  let date = new Date();
  let fileName =
    "settings-" +
    date.getFullYear() +
    "-" +
    (date.getMonth() + 1) +
    "-" +
    date.getDate() +
    ".json";
  downBlob(blob, fileName);
  notyf.success(translations[locale]["exportSuccTip"]);
};
document.getElementById("importSetInput").onchange = function () {
  let file = this.files[0];
  blobToText(file).then((text) => {
    try {
      let json = JSON.parse(text);
      let keys = Object.keys(json);
      for (let i = 0; i < localSetKeys.length; i++) {
        let key = localSetKeys[i];
        let val = json[key];
        if (val !== void 0) localStorage.setItem(key, val);
        else localStorage.removeItem(key);
      }
      initSetting();
      initVoiceVal();
      speechServiceEle.dispatchEvent(new Event("change"));
      initRecSetting();
      initHotKey();
      initLang();
      checkStorage();
      notyf.success(translations[locale]["importSuccTip"]);
    } catch (e) {
      notyf.error(translations[locale]["importFailTip"]);
    }
    this.value = "";
  });
};
document.getElementById("resetSet").onclick = () => {
  if (confirmAction(translations[locale]["resetSetTip"])) {
    endAll();
    if (existVoice === 3) localStorage.removeItem(azureRegion + "VoiceData");
    if (existRec === 2) localStorage.removeItem(azureRecRegion + "RecData");
    let data = {};
    for (let i = 0; i < localSetKeys.length; i++) {
      let key = localSetKeys[i];
      let val = localStorage.removeItem(key);
    }
    initSetting();
    initVoiceVal();
    speechServiceEle.dispatchEvent(new Event("change"));
    initRecSetting();
    initHotKey();
    initLang();
    checkStorage();
    notyf.success(translations[locale]["resetSetSuccTip"]);
  }
};
const endAll = () => {
  endSpeak();
  if (editingIdx !== void 0) resumeSend();
  if (loading) stopLoading();
};
const processIdx = (plus) => {
  if (currentVoiceIdx !== void 0) currentVoiceIdx += plus;
  if (editingIdx !== void 0) editingIdx += plus;
};
const hotKeyVals = {};
const ctrlHotKeyEv = (ev) => {
  if (ev.ctrlKey || ev.metaKey) {
    switch (ev.key.toLowerCase()) {
      case hotKeyVals["Nav"]:
        ev.preventDefault();
        toggleNavEv();
        return false;
      case hotKeyVals["Search"]:
        ev.preventDefault();
        searchChatEle.focus();
        return false;
      case hotKeyVals["Input"]:
        ev.preventDefault();
        inputAreaEle.focus();
        return false;
      case hotKeyVals["NewChat"]:
        ev.preventDefault();
        newChatEle.dispatchEvent(new MouseEvent("click"));
        return false;
      case hotKeyVals["ClearChat"]:
        ev.preventDefault();
        clearEle.dispatchEvent(new MouseEvent("click"));
        return false;
      case hotKeyVals["VoiceRec"]:
        if (supportRec) {
          ev.preventDefault();
          toggleRecEv();
        }
        return false;
      case hotKeyVals["VoiceSpeak"]:
        ev.preventDefault();
        speechEvent(systemRole ? 1 : 0);
        return false;
    }
  }
};
const ctrlAltHotKeyEv = (ev) => {
  if ((ev.ctrlKey || ev.metaKey) && ev.altKey) {
    switch (ev.key.toLowerCase()) {
      case hotKeyVals["Window"]:
        ev.preventDefault();
        toggleFull.dispatchEvent(new Event("click"));
        return false;
      case hotKeyVals["Theme"]:
        ev.preventDefault();
        lightEle.dispatchEvent(new Event("click"));
        return false;
      case hotKeyVals["Lang"]:
        ev.preventDefault();
        let idx = localeList.indexOf(locale) + 1;
        if (idx === localeList.length) idx = 0;
        locale = localeList[idx];
        setLang();
        changeLocale();
        return false;
    }
  }
};
const listKey = [
  "Nav",
  "Search",
  "Input",
  "NewChat",
  "ClearChat",
  "VoiceRec",
  "VoiceSpeak",
  "Window",
  "Theme",
  "Lang",
];
const ctrlKeyIdx = 7;
const defKeyVal = ["b", "k", "i", "e", "r", "q", "s", "u", "t", "l"];
const initHotKey = () => {
  let localKeysObj = {};
  let localKeys = localStorage.getItem("hotKeys");
  if (localKeys) {
    try {
      localKeysObj = JSON.parse(localKeys);
    } catch (e) {}
  }
  let pre1 = isApple ? "⌘ + " : "Ctrl + ";
  let pre2 = isApple ? "⌘ + ⌥ + " : "Ctrl + Alt + ";
  for (let i = 0; i < listKey.length; i++) {
    let key = listKey[i];
    if (key === "VoiceRec" && !supportRec) continue;
    let ele = window["hotKey" + key];
    for (let j = 0; j < 26; j++) {
      // top-level hotkey, can't overwrite
      if (i < ctrlKeyIdx && (j === 13 || j === 19 || j === 22)) continue;
      let val = String.fromCharCode(j + 97);
      ele.options.add(
        new Option((i < ctrlKeyIdx ? pre1 : pre2) + val.toUpperCase(), val)
      );
    }
    hotKeyVals[key] = ele.value = localKeysObj[key] || defKeyVal[i];
    ele.onchange = () => {
      if (hotKeyVals[key] === ele.value) return;
      let exist = listKey.find((item, idx) => {
        return (
          (i < ctrlKeyIdx ? idx < ctrlKeyIdx : idx >= ctrlKeyIdx) &&
          hotKeyVals[item] === ele.value
        );
      });
      if (exist) {
        ele.value = hotKeyVals[key];
        notyf.error(translations[locale]["hotkeyConflict"]);
        return;
      }
      hotKeyVals[key] = ele.value;
      localStorage.setItem("hotKeys", JSON.stringify(hotKeyVals));
    };
  }
};
initHotKey();
document.addEventListener("keydown", ctrlHotKeyEv);
document.addEventListener("keydown", ctrlAltHotKeyEv);
const initSetting = () => {
  const modelsEle = Array.from(modelSetEle.children);
  let localModelName = localStorage.getItem("modelVersion");
  let isVailModel = modelsEle.some(
    (item) => item.dataset.value === localModelName
  );
  modelVersion = isVailModel ? localModelName : "gpt-3.5-turbo";
  const applyModelVersion = () => {
    let activedEle = modelSetEle.querySelector(".activeModel");
    if (activedEle) activedEle.classList.remove("activeModel");
    activedEle = modelSetEle.querySelector(`[data-value="${modelVersion}"]`);
    activedEle.classList.add("activeModel");
    modelVer.textContent = activedEle.dataset.ver;
    if (modelVersion.startsWith("gpt")) {
      modelType = 1;
      modelName.textContent = "ChatGPT";
    } else if (modelVersion.startsWith("gemini")) {
      modelType = 2;
      modelName.textContent = "Gemini";
    } else {
      modelType = 3;
      modelName.textContent = "Claude";
    }
  };
  applyModelVersion();
  modelSetEle.onclick = (ev) => {
    if (ev.target.classList.contains("modelSingle")) {
      modelVersion = ev.target.dataset.value;
      localStorage.setItem("modelVersion", modelVersion);
      applyModelVersion();
      modelSetEle.style.display = "none";
      selectorEle.classList.remove("showModels");
    }
  };
  const apiHostEle = document.getElementById("apiHostInput");
  const geminiHostEle = document.getElementById("geminiApiHostInput");
  const claudeHostEle = document.getElementById("claudeApiHostInput");
  const apiSelectEle = document.getElementById("apiSelect");
  let localApiSelect = localStorage.getItem("APISelect");
  if (localApiSelect) {
    try {
      apiSelects = JSON.parse(localApiSelect);
    } catch (e) {
      apiSelects.length = 0;
    }
  } else {
    apiSelects.length = 0;
  }
  let localGeminiApiSelect = localStorage.getItem("GeminiAPISelect");
  if (localGeminiApiSelect) {
    try {
      geminiApiSelects = JSON.parse(localGeminiApiSelect);
    } catch (e) {
      geminiApiSelects.length = 0;
    }
  } else {
    geminiApiSelects.length = 0;
  }
  let localClaudeApiSelect = localStorage.getItem("ClaudeAPISelect");
  if (localClaudeApiSelect) {
    try {
      claudeApiSelects = JSON.parse(localClaudeApiSelect);
    } catch (e) {
      claudeApiSelects.length = 0;
    }
  } else {
    claudeApiSelects.length = 0;
  }
  let selApiSelects = apiSelects;
  let selApiKey = "APISelect";
  let selApiEle = apiHostEle;
  const delApiOption = function (ev) {
    ev.preventDefault();
    ev.stopPropagation();
    let index = Array.prototype.indexOf.call(
      apiSelectEle.children,
      this.parentElement
    );
    selApiSelects.splice(index, 1);
    this.parentElement.remove();
    localStorage.setItem(selApiKey, JSON.stringify(selApiSelects));
    if (!selApiSelects.includes(selApiEle.value)) {
      selApiEle.value = selApiSelects[0] || "";
      selApiEle.dispatchEvent(new Event("change"));
    }
    if (!selApiSelects.length) apiSelectEle.style.display = "none";
  };
  const appendApiOption = () => {
    apiSelects.push(apiHost);
    initApiOption(apiHost);
    localStorage.setItem("APISelect", JSON.stringify(apiSelects));
  };
  const appendGeminiApiOption = () => {
    geminiApiSelects.push(geminiApiHost);
    initApiOption(geminiApiHost);
    localStorage.setItem("GeminiAPISelect", JSON.stringify(geminiApiSelects));
  };
  const appendClaudeApiOption = () => {
    claudeApiSelects.push(claudeApiHost);
    initApiOption(claudeApiHost);
    localStorage.setItem("ClaudeAPISelect", JSON.stringify(claudeApiSelects));
  };
  const selApiOption = function (ev) {
    ev.preventDefault();
    ev.stopPropagation();
    apiSelectEle.style.display = "none";
    let index = Array.prototype.indexOf.call(apiSelectEle.children, this);
    selApiEle.value = selApiSelects[index];
    selApiEle.dispatchEvent(new Event("change"));
  };
  const initApiOption = (api) => {
    let optionEle = document.createElement("div");
    optionEle.onclick = selApiOption;
    let textEle = document.createElement("span");
    textEle.textContent = api;
    optionEle.appendChild(textEle);
    let delEle = document.createElement("div");
    delEle.className = "delApiOption";
    delEle.onmousedown = delApiOption;
    delEle.innerHTML = `<svg width="24" height="24"><use xlink:href="#closeIcon" /></svg>`;
    optionEle.appendChild(delEle);
    apiSelectEle.appendChild(optionEle);
  };
  const initApiSelectEle = () => {
    apiSelectEle.innerHTML = "";
    for (let i = 0; i < selApiSelects.length; i++) {
      initApiOption(selApiSelects[i]);
    }
  };
  apiHostEle.onfocus =
    geminiHostEle.onfocus =
    claudeHostEle.onfocus =
      () => {
        let type = document
          .querySelector(".modelSwitch")
          .querySelector(".activeSwitch").dataset.id;
        if (type === "gptOption") {
          selApiSelects = apiSelects;
          selApiKey = "APISelect";
          selApiEle = apiHostEle;
        } else if (type === "geminiOption") {
          selApiSelects = geminiApiSelects;
          selApiKey = "GeminiAPISelect";
          selApiEle = geminiHostEle;
        } else {
          selApiSelects = claudeApiSelects;
          selApiKey = "ClaudeAPISelect";
          selApiEle = claudeHostEle;
        }
        initApiSelectEle();
        if (selApiSelects.length) apiSelectEle.style.display = "block";
      };
  apiHostEle.onblur =
    geminiHostEle.onblur =
    claudeHostEle.onblur =
      (ev) => {
        if (!(ev.relatedTarget && apiSelectEle.contains(ev.relatedTarget)))
          apiSelectEle.style.display = "none";
      };
  let localApiHost = localStorage.getItem("APIHost");
  apiHost = apiHostEle.value =
    envAPIEndpoint || localApiHost || apiHostEle.getAttribute("value") || "";
  apiHostEle.onchange = () => {
    apiHost = apiHostEle.value;
    if (apiHost && apiSelects.indexOf(apiHost) === -1) appendApiOption();
    localStorage.setItem("APIHost", apiHost);
  };
  apiHostEle.dispatchEvent(new Event("change"));
  const keyEle = document.getElementById("keyInput");
  let localKey = localStorage.getItem("APIKey");
  customAPIKey = keyEle.value =
    envAPIKey || localKey || keyEle.getAttribute("value") || "";
  keyEle.onchange = () => {
    customAPIKey = keyEle.value;
    localStorage.setItem("APIKey", customAPIKey);
  };
  keyEle.dispatchEvent(new Event("change"));
  const modelEle = document.getElementById("modelInput");
  let localModel = localStorage.getItem("APIModel");
  customAPIModel = modelEle.value =
    envAPIModel || localModel || modelEle.getAttribute("value") || "";
  modelEle.onchange = () => {
    customAPIModel = modelEle.value;
    localStorage.setItem("APIModel", customAPIModel);
  };
  modelEle.dispatchEvent(new Event("change"));

  let localGeminiApiHost = localStorage.getItem("GeminiAPIHost");
  geminiApiHost = geminiHostEle.value =
    envGeminiAPIEndpoint ||
    localGeminiApiHost ||
    geminiHostEle.getAttribute("value") ||
    "";
  geminiHostEle.onchange = () => {
    geminiApiHost = geminiHostEle.value;
    if (geminiApiHost && geminiApiSelects.indexOf(geminiApiHost) === -1)
      appendGeminiApiOption();
    localStorage.setItem("GeminiAPIHost", geminiApiHost);
  };
  geminiHostEle.dispatchEvent(new Event("change"));
  const geminiKeyEle = document.getElementById("geminiKeyInput");
  let localGeminiKey = localStorage.getItem("GeminiAPIKey");
  geminiAPIKey = geminiKeyEle.value =
    envGeminiAPIKey ||
    localGeminiKey ||
    geminiKeyEle.getAttribute("value") ||
    "";
  geminiKeyEle.onchange = () => {
    geminiAPIKey = geminiKeyEle.value;
    localStorage.setItem("GeminiAPIKey", geminiAPIKey);
  };
  geminiKeyEle.dispatchEvent(new Event("change"));
  const geminiModelEle = document.getElementById("geminiModelInput");
  let localGeminiModel = localStorage.getItem("GeminiAPIModel");
  geminiAPIModel = geminiModelEle.value =
    envGeminiAPIModel ||
    localGeminiModel ||
    geminiModelEle.getAttribute("value") ||
    "";
  geminiModelEle.onchange = () => {
    geminiAPIModel = geminiModelEle.value;
    localStorage.setItem("GeminiAPIModel", geminiAPIModel);
  };
  geminiModelEle.dispatchEvent(new Event("change"));

  let localClaudeApiHost = localStorage.getItem("ClaudeAPIHost");
  claudeApiHost = claudeHostEle.value =
    envClaudeAPIEndpoint ||
    localClaudeApiHost ||
    claudeHostEle.getAttribute("value") ||
    "";
  claudeHostEle.onchange = () => {
    claudeApiHost = claudeHostEle.value;
    if (claudeApiHost && claudeApiSelects.indexOf(claudeApiHost) === -1)
      appendClaudeApiOption();
    localStorage.setItem("ClaudeAPIHost", claudeApiHost);
  };
  claudeHostEle.dispatchEvent(new Event("change"));
  const claudeKeyEle = document.getElementById("claudeKeyInput");
  let localClaudeKey = localStorage.getItem("ClaudeAPIKey");
  claudeAPIKey = claudeKeyEle.value =
    envClaudeAPIKey ||
    localClaudeKey ||
    claudeKeyEle.getAttribute("value") ||
    "";
  claudeKeyEle.onchange = () => {
    claudeAPIKey = claudeKeyEle.value;
    localStorage.setItem("ClaudeAPIKey", claudeAPIKey);
  };
  claudeKeyEle.dispatchEvent(new Event("change"));
  const claudeModelEle = document.getElementById("claudeModelInput");
  let localClaudeModel = localStorage.getItem("ClaudeAPIModel");
  claudeAPIModel = claudeModelEle.value =
    envClaudeAPIModel ||
    localClaudeModel ||
    claudeModelEle.getAttribute("value") ||
    "";
  claudeModelEle.onchange = () => {
    claudeAPIModel = claudeModelEle.value;
    localStorage.setItem("ClaudeAPIModel", claudeAPIModel);
  };
  claudeModelEle.dispatchEvent(new Event("change"));

  const updateAvatar = () => {
    setAvatarPre.src = userAvatar;
    chatlog.querySelectorAll(".request>.chatAvatar").forEach((ele) => {
      ele.children[0].src = userAvatar;
    });
  };
  let localAvatar = localStorage.getItem("userAvatar");
  userAvatar =
    setAvatarPre.src =
    setAvatar.value =
      localAvatar ||
      setAvatar.getAttribute("value") ||
      "https://cdn.jsdelivr.net/gh/xqdoo00o/chatgpt-web@main/avatar.jpg";
  setAvatar.onchange = () => {
    userAvatar = setAvatar.value;
    localStorage.setItem("userAvatar", userAvatar);
    updateAvatar();
  };
  setAvatar.dispatchEvent(new Event("change"));
  let localSystem = localStorage.getItem("system");
  systemEle.onchange = () => {
    systemRole = systemEle.value;
    localStorage.setItem("system", systemRole);
    if (systemRole) {
      if (data[0] && data[0].role === "system") {
        data[0].content = systemRole;
      } else {
        data.unshift({ role: "system", content: systemRole });
        processIdx(1);
      }
    } else if (data[0] && data[0].role === "system") {
      data.shift();
      processIdx(-1);
    }
    updateChats();
  };
  if (systemRole === void 0) {
    systemRole = systemEle.value = localSystem || presetRoleData.default || "";
    if (systemRole) {
      data.unshift({ role: "system", content: systemRole });
      processIdx(1);
      updateChats();
    }
  }
  preEle.onchange = () => {
    let val = preEle.value;
    if (val && presetRoleData[val]) {
      systemEle.value = presetRoleData[val];
    } else {
      systemEle.value = "";
    }
    systemEle.dispatchEvent(new Event("change"));
    systemEle.focus();
  };
  const topEle = document.getElementById("top_p");
  let localTop = localStorage.getItem("top_p");
  topEle.value = roleNature = parseFloat(
    localTop || topEle.getAttribute("value")
  );
  topEle.oninput = () => {
    topEle.style.backgroundSize =
      ((topEle.value - topEle.min) * 100) / (topEle.max - topEle.min) +
      "% 100%";
    roleNature = parseFloat(topEle.value);
    localStorage.setItem("top_p", topEle.value);
  };
  topEle.dispatchEvent(new Event("input"));
  const tempEle = document.getElementById("temp");
  let localTemp = localStorage.getItem("temp");
  tempEle.value = roleTemp = parseFloat(
    localTemp || tempEle.getAttribute("value")
  );
  tempEle.oninput = () => {
    tempEle.style.backgroundSize =
      ((tempEle.value - tempEle.min) * 100) / (tempEle.max - tempEle.min) +
      "% 100%";
    roleTemp = parseFloat(tempEle.value);
    localStorage.setItem("temp", tempEle.value);
  };
  tempEle.dispatchEvent(new Event("input"));
  const convWEle = document.getElementById("convWidth");
  const styleSheet = document.styleSheets[0];
  convWEle.oninput = () => {
    let type = isFull ? 1 : 0;
    convWEle.style.backgroundSize =
      ((convWEle.value - convWEle.min) * 100) / (convWEle.max - convWEle.min) +
      "% 100%";
    convWidth[type] = parseInt(convWEle.value);
    localStorage.setItem("convWidth" + type, convWEle.value);
    styleSheet.deleteRule(0);
    styleSheet.deleteRule(0);
    styleSheet.insertRule(`.bottom_wrapper{max-width:${convWidth[type]}%;}`, 0);
    styleSheet.insertRule(
      `.requestBody,.response .markdown-body{max-width:calc(${convWidth[type]}% - 84px);}`,
      0
    );
  };
  const setConvValue = () => {
    let type = isFull ? 1 : 0;
    let localConv = localStorage.getItem("convWidth" + type);
    convWEle.value = parseInt(localConv || (type ? "60" : "100"));
    convWEle.dispatchEvent(new Event("input"));
  };
  const fullFunc = () => {
    isFull = windowEle.classList.contains("full_window");
    localStorage.setItem("fullWindow", isFull);
    setConvValue();
    toggleFull.title = isFull
      ? translations[locale]["winedWin"]
      : translations[locale]["fullWin"];
    toggleFull.children[0].children[0].setAttributeNS(
      "http://www.w3.org/1999/xlink",
      "href",
      isFull ? "#collapseFullIcon" : "#expandFullIcon"
    );
  };
  toggleFull.onclick = () => {
    windowEle.classList.toggle("full_window");
    fullFunc();
  };
  let localFull = localStorage.getItem("fullWindow");
  if (localFull && localFull === "true") {
    if (!windowEle.classList.contains("full_window")) {
      windowEle.classList.add("full_window");
      fullFunc();
    }
  } else if (windowEle.classList.contains("full_window")) {
    windowEle.classList.remove("full_window");
    fullFunc();
  } else {
    setConvValue();
  }
  const speedEle = document.getElementById("textSpeed");
  let localSpeed = localStorage.getItem("textSpeed");
  speedEle.value = localSpeed || speedEle.getAttribute("value");
  textSpeed = parseFloat(speedEle.min) + (speedEle.max - speedEle.value);
  speedEle.oninput = () => {
    speedEle.style.backgroundSize =
      ((speedEle.value - speedEle.min) * 100) / (speedEle.max - speedEle.min) +
      "% 100%";
    textSpeed = parseFloat(speedEle.min) + (speedEle.max - speedEle.value);
    localStorage.setItem("textSpeed", speedEle.value);
  };
  speedEle.dispatchEvent(new Event("input"));
  if (localStorage.getItem("enableCont") != null) {
    // fallback old cont
    if (localStorage.getItem("enableCont") === "false")
      localStorage.setItem("contLength", 0);
    localStorage.removeItem("enableCont");
  }
  const contLenEle = document.getElementById("contLength");
  let localContLen = localStorage.getItem("contLength");
  contLenEle.value = contLen = parseInt(
    localContLen || contLenEle.getAttribute("value")
  );
  contLenEle.oninput = () => {
    contLenEle.style.backgroundSize =
      ((contLenEle.value - contLenEle.min) * 100) /
        (contLenEle.max - contLenEle.min) +
      "% 100%";
    contLen = parseInt(contLenEle.value);
    contLenWrap.textContent = contLen;
    localStorage.setItem("contLength", contLenEle.value);
  };
  contLenEle.dispatchEvent(new Event("input"));
  const longEle = document.getElementById("enableLongReply");
  let localLong = localStorage.getItem("enableLongReply");
  longEle.checked = enableLongReply =
    (localLong || longEle.getAttribute("checked")) === "true";
  longEle.onchange = () => {
    enableLongReply = longEle.checked;
    localStorage.setItem("enableLongReply", enableLongReply);
  };
  longEle.dispatchEvent(new Event("change"));
  let localPin = localStorage.getItem("pinNav");
  if (window.innerWidth > 800 && !(localPin && localPin === "false")) {
    document.body.classList.add("show-nav");
  }
  const setDarkTheme = (is) => {
    let cssEle = document.body.getElementsByTagName("link")[0];
    cssEle.href = cssEle.href.replace(
      is ? "light" : "dark",
      is ? "dark" : "light"
    );
    let hlCssEle = document.body.getElementsByTagName("link")[1];
    hlCssEle.href = hlCssEle.href.replace(
      is ? "github" : "github-dark",
      is ? "github-dark" : "github"
    );
    justDarkTheme(is);
  };
  const handleAutoMode = (ele) => {
    if (ele.checked) {
      autoThemeMode = parseInt(ele.value);
      localStorage.setItem("autoThemeMode", autoThemeMode);
      initAutoTime();
      if (autoThemeMode) {
        if (customDarkOut !== void 0) {
          clearTimeout(customDarkOut);
          customDarkOut = void 0;
        }
        setDarkTheme(darkMedia.matches);
      } else {
        checkCustomTheme();
      }
    }
  };
  autoTheme0.onchange = autoTheme1.onchange = function () {
    handleAutoMode(this);
  };
  const handleAutoTime = (ele, idx) => {
    let otherIdx = 1 - idx;
    if (ele.value !== customDarkTime[otherIdx]) {
      customDarkTime[idx] = ele.value;
      localStorage.setItem("customDarkTime", JSON.stringify(customDarkTime));
      checkCustomTheme();
    } else {
      ele.value = customDarkTime[idx];
      notyf.error(translations[locale]["customDarkTip"]);
    }
  };
  customStart.onchange = function () {
    handleAutoTime(this, 0);
  };
  customEnd.onchange = function () {
    handleAutoTime(this, 1);
  };
  const initAutoTime = () => {
    customAutoSet.style.display = autoThemeMode === 0 ? "block" : "none";
    if (autoThemeMode === 0) {
      customStart.value = customDarkTime[0];
      customEnd.value = customDarkTime[1];
    }
  };
  const initAutoThemeEle = () => {
    autoThemeEle.querySelector("#autoTheme" + autoThemeMode).checked = true;
    initAutoTime();
  };
  const checkCustomTheme = () => {
    if (customDarkOut !== void 0) clearTimeout(customDarkOut);
    let date = new Date();
    let nowTime = date.getTime();
    let start = customDarkTime[0].split(":");
    let startTime = new Date().setHours(start[0], start[1], 0, 0);
    let end = customDarkTime[1].split(":");
    let endTime = new Date().setHours(end[0], end[1], 0, 0);
    let order = endTime > startTime;
    let isDark = order
      ? nowTime > startTime && endTime > nowTime
      : !(nowTime > endTime && startTime > nowTime);
    let nextChange = isDark ? endTime - nowTime : startTime - nowTime;
    if (nextChange < 0) nextChange += dayMs;
    setDarkTheme(isDark);
    customDarkOut = setTimeout(() => {
      checkCustomTheme();
    }, nextChange);
  };
  const setDarkMode = () => {
    if (customDarkOut !== void 0) {
      clearTimeout(customDarkOut);
      customDarkOut = void 0;
    }
    autoThemeEle.style.display = "none";
    let themeClass, title;
    if (themeMode === 2) {
      autoThemeEle.style.display = "block";
      if (autoThemeMode) {
        setDarkTheme(darkMedia.matches);
      } else {
        checkCustomTheme();
        initAutoThemeEle();
      }
      themeClass = "autoTheme";
      title = translations[locale]["autoWord"];
    } else if (themeMode === 1) {
      setDarkTheme(false);
      themeClass = "lightTheme";
      title = translations[locale]["lightTheme"];
    } else {
      setDarkTheme(true);
      themeClass = "darkTheme";
      title = translations[locale]["darkTheme"];
    }
    localStorage.setItem("themeMode", themeMode);
    setLightEle.className = "setDetail themeDetail " + themeClass;
    lightEle.children[0].children[0].setAttributeNS(
      "http://www.w3.org/1999/xlink",
      "href",
      "#" + themeClass + "Icon"
    );
    lightEle.title = title;
  };
  lightEle.onclick = () => {
    themeMode = themeMode - 1;
    if (themeMode === -1) themeMode = 2;
    setDarkMode();
  };
  setLightEle.onclick = (ev) => {
    let idx = Array.prototype.indexOf.call(setLightEle.children, ev.target);
    if (themeMode !== idx) {
      themeMode = idx;
      setDarkMode();
    }
  };
  let localTheme = localStorage.getItem("themeMode");
  themeMode = parseInt(localTheme || "1");
  let localAutoTheme = localStorage.getItem("autoThemeMode");
  autoThemeMode = parseInt(localAutoTheme || "1");
  let localCustomDark = localStorage.getItem("customDarkTime");
  customDarkTime = JSON.parse(localCustomDark || '["21:00", "07:00"]');
  setDarkMode();
  darkMedia.onchange = (e) => {
    if (themeMode === 2 && autoThemeMode) setDarkTheme(e.matches);
  };
  const caseSearchEle = document.getElementById("matchCaseSearch");
  let localSearchFlag = localStorage.getItem("searchFlag") || "0";
  isCaseSearch = Boolean(localSearchFlag & 1);
  caseSearchEle.classList.toggle("seledSearch", isCaseSearch);
  caseSearchEle.onclick = () => {
    isCaseSearch = caseSearchEle.classList.toggle("seledSearch");
    localStorage.setItem("searchFlag", ~~isCaseSearch);
    if (searchChatEle.value.length) toSearchChats();
  };
};
initSetting();
document.getElementById("loadMask").style.display = "none";
const closeEvent = (ev) => {
  if (settingEle.contains(ev.target)) return;
  if (!dialogEle.contains(ev.target)) {
    dialogEle.style.display = "none";
    document.removeEventListener("mousedown", closeEvent, true);
    settingEle.classList.remove("showSetting");
    stopTestVoice();
  }
};
settingEle.onmousedown = () => {
  dialogEle.style.display =
    dialogEle.style.display === "block" ? "none" : "block";
  if (dialogEle.style.display === "block") {
    document.addEventListener("mousedown", closeEvent, true);
    settingEle.classList.add("showSetting");
  } else {
    document.removeEventListener("mousedown", closeEvent, true);
    settingEle.classList.remove("showSetting");
  }
};
const modelCloseEvent = (ev) => {
  if (selectorEle.contains(ev.target)) return;
  if (!modelSetEle.contains(ev.target)) {
    document.removeEventListener("mousedown", modelCloseEvent, true);
    modelSetEle.style.display = "none";
    selectorEle.classList.remove("showModels");
  }
};
selectorEle.onmousedown = () => {
  modelSetEle.style.display =
    modelSetEle.style.display === "block" ? "none" : "block";
  if (modelSetEle.style.display === "block") {
    document.addEventListener("mousedown", modelCloseEvent, true);
    selectorEle.classList.add("showModels");
  } else {
    document.removeEventListener("mousedown", modelCloseEvent, true);
    selectorEle.classList.remove("showModels");
  }
};
let delayId;
const delay = () => {
  return new Promise((resolve) => (delayId = setTimeout(resolve, textSpeed))); //打字机时间间隔
};
const getTime = () => {
  return existVoice === 3 ? new Date().toISOString() : new Date().toString();
};
const getWSPre = (date, requestId) => {
  let osPlatform = typeof window !== "undefined" ? "Browser" : "Node";
  osPlatform += "/" + navigator.platform;
  let osName = navigator.userAgent;
  let osVersion = navigator.appVersion;
  return `Path: speech.config\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${date}\r\nContent-Type: application/json\r\n\r\n{"context":{"system":{"name":"SpeechSDK","version":"1.35.0","build":"JavaScript","lang":"JavaScript"},"os":{"platform":"${osPlatform}","name":"${osName}","version":"${osVersion}"}}}`;
};
const getWSAudio = (date, requestId) => {
  return existVoice === 3
    ? `Path: synthesis.context\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${date}\r\nContent-Type: application/json\r\n\r\n{"synthesis":{"audio":{"metadataOptions":{"bookmarkEnabled":false,"punctuationBoundaryEnabled":"false","sentenceBoundaryEnabled":"false","sessionEndEnabled":true,"visemeEnabled":false,"wordBoundaryEnabled":"false"},"outputFormat":"${voiceFormat}"},"language":{"autoDetection":false}}}`
    : `X-Timestamp:${date}\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":"false","wordBoundaryEnabled":"true"},"outputFormat":"${voiceFormat}"}}}}`;
};
const getWSText = (
  date,
  requestId,
  lang,
  voice,
  volume,
  rate,
  pitch,
  style,
  role,
  msg
) => {
  let fmtVolume = (volume >= 1 ? "+" : "") + (volume * 100 - 100) + "%";
  let fmtRate = (rate >= 1 ? "+" : "") + (rate * 100 - 100) + "%";
  let fmtPitch = (pitch >= 1 ? "+" : "") + (pitch - 1) + "Hz";
  msg = getEscape(msg);
  if (existVoice === 3) {
    let fmtStyle = style ? ` style="${style}"` : ` style="Default"`;
    let fmtRole = role ? ` role="${role}"` : "";
    let fmtExpress = fmtStyle + fmtRole;
    return `Path: ssml\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${date}\r\nContent-Type: application/ssml+xml\r\n\r\n<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" xml:lang="${lang}"><voice name="${voice}"><lang xml:lang="${lang}"><s /><mstts:express-as${fmtExpress}><prosody pitch="${fmtPitch}" rate="${fmtRate}" volume="${fmtVolume}">${msg}</prosody></mstts:express-as><s /></lang></voice></speak>`;
  } else {
    return `X-RequestId:${requestId}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${date}Z\r\nPath:ssml\r\n\r\n<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${lang}"><voice name="${voice}"><prosody pitch="${fmtPitch}" rate="${fmtRate}" volume="${fmtVolume}">${msg}</prosody></voice></speak>`;
  }
};
const getAzureWSURL = () => {
  return `wss://${azureRegion}.tts.speech.microsoft.com/cognitiveservices/websocket/v1?Ocp-Apim-Subscription-Key=${azureKey}`;
};
const edgeTTSURL =
  "wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4";
const resetSpeakIcon = () => {
  if (currentVoiceIdx !== void 0) {
    chatlog.children[
      systemRole ? currentVoiceIdx - 1 : currentVoiceIdx
    ].classList.remove("showVoiceCls");
    chatlog.children[
      systemRole ? currentVoiceIdx - 1 : currentVoiceIdx
    ].lastChild.lastChild.className = "voiceCls readyVoice";
  }
};
const endSpeak = () => {
  resetSpeakIcon();
  currentVoiceIdx = void 0;
  if (voiceIns && voiceIns instanceof Audio) {
    voiceIns.pause();
    voiceIns.currentTime = 0;
    URL.revokeObjectURL(voiceIns.src);
    voiceIns.removeAttribute("src");
    voiceIns.onended = voiceIns.onerror = null;
    sourceBuffer = void 0;
    speechPushing = false;
    if (voiceSocket && voiceSocket["pending"]) {
      voiceSocket.close();
    }
    if (autoVoiceSocket && autoVoiceSocket["pending"]) {
      autoVoiceSocket.close();
    }
    speechQuene.length = 0;
    autoPlayingIdx = 0;
    autoMediaSource = void 0;
    voiceContentQuene = [];
    voiceEndFlagQuene = [];
    voiceBlobURLQuene = [];
    autoOnlineVoiceFlag = false;
  } else if (supportSpe) {
    speechSynthesis.cancel();
  }
};
const speakEvent = (ins, force = true, end = false) => {
  return new Promise((res, rej) => {
    ins.onerror = () => {
      if (end) {
        endSpeak();
      } else if (force) {
        resetSpeakIcon();
      }
      res();
    };
    if (ins instanceof Audio) {
      ins.onended = ins.onerror;
      ins.play();
    } else {
      ins.onend = ins.onerror;
      speechSynthesis.speak(voiceIns);
    }
  });
};
let voiceData = {};
let voiceSocket;
let speechPushing = false;
let speechQuene = [];
let sourceBuffer;
speechQuene.push = function (buffer) {
  if (!speechPushing && sourceBuffer && !sourceBuffer.updating) {
    speechPushing = true;
    sourceBuffer.appendBuffer(buffer);
  } else {
    Array.prototype.push.call(this, buffer);
  }
};
const initSocket = () => {
  return new Promise((res, rej) => {
    let url = existVoice === 3 ? getAzureWSURL() : edgeTTSURL;
    if (!voiceSocket || voiceSocket.readyState > 1 || voiceSocket.url !== url) {
      if (voiceSocket && voiceSocket.readyState === 1) voiceSocket.close(1000);
      voiceSocket = new WebSocket(url);
      voiceSocket.binaryType = "arraybuffer";
      voiceSocket.onopen = () => {
        res();
      };
      voiceSocket.onerror = () => {
        rej();
      };
    } else {
      return res();
    }
  });
};
const initStreamVoice = (mediaSource) => {
  return new Promise((r, j) => {
    Promise.all([
      initSocket(),
      new Promise((res) => {
        mediaSource.onsourceopen = () => {
          res();
        };
      }),
    ]).then(() => {
      r();
    });
  });
};
let downQuene = {};
let downSocket;
const downBlob = (blob, name) => {
  let a = document.createElement("a");
  a.download = name;
  let url = URL.createObjectURL(blob);
  a.href = url;
  a.click();
  setTimeout(() => {
    URL.revokeObjectURL(url);
  }, 1000);
  a = null;
};
const initDownSocket = () => {
  return new Promise((res, rej) => {
    let url = existVoice === 3 ? getAzureWSURL() : edgeTTSURL;
    if (!downSocket || downSocket.readyState > 1 || downSocket.url !== url) {
      if (downSocket && downSocket.readyState === 1) downSocket.close(1000);
      downSocket = new WebSocket(url);
      downSocket.binaryType = "arraybuffer";
      downSocket.onopen = () => {
        res();
      };
      downSocket.onmessage = (e) => {
        if (e.data instanceof ArrayBuffer) {
          let text = new TextDecoder().decode(e.data.slice(0, voicePreLen));
          let reqIdx = text.indexOf(":");
          let uuid = text.slice(reqIdx + 1, reqIdx + 33);
          downQuene[uuid]["blob"].push(e.data.slice(voicePreLen));
        } else if (e.data.indexOf("Path:turn.end") !== -1) {
          let reqIdx = e.data.indexOf(":");
          let uuid = e.data.slice(reqIdx + 1, reqIdx + 33);
          let blob = new Blob(downQuene[uuid]["blob"], {
            type: voiceMIME,
          });
          let key = downQuene[uuid]["key"];
          let name = downQuene[uuid]["name"];
          if (blob.size === 0) {
            notyf.open({
              type: "warning",
              message: translations[locale]["cantSpeechTip"],
            });
            return;
          }
          voiceData[key] = blob;
          if (downQuene[uuid]["isTest"]) {
            testVoiceBlob = blob;
            playTestAudio();
          } else {
            downBlob(blob, name.slice(0, 16) + voiceSuffix);
          }
        }
      };
      downSocket.onerror = () => {
        rej();
      };
    } else {
      return res();
    }
  });
};
const getOpenAIVoice = async (input, voice, speed) => {
  let url =
    apiHost +
    (apiHost.length && !apiHost.endsWith("/") ? "/" : "") +
    "v1/audio/speech";
  let headers = { "Content-Type": "application/json" };
  if (customAPIKey) headers["Authorization"] = "Bearer " + customAPIKey;
  let body = JSON.stringify({
    model: "tts-1",
    input,
    voice,
    response_format: "aac",
    speed,
  });
  let controller = new AbortController();
  let controllerId = setTimeout(() => {
    notyf.error(translations[locale]["timeoutTip"]);
    controller.abort();
  }, 20000);
  try {
    const res = await fetch(url, {
      method: "POST",
      headers,
      body,
      signal: controller.signal,
    });
    clearTimeout(controllerId);
    if (res.status === 200) {
      return await res.blob();
    } else {
      notyf.open({
        type: "warning",
        message: translations[locale]["cantSpeechTip"],
      });
    }
  } catch (e) {}
};
let testVoiceBlob;
let testVoiceIns;
const playTestAudio = () => {
  if (existVoice >= 2) {
    if (!testVoiceIns || testVoiceIns instanceof Audio === false) {
      testVoiceIns = new Audio();
      testVoiceIns.onended = testVoiceIns.onerror = () => {
        stopTestVoice();
      };
    }
    testVoiceIns.src = URL.createObjectURL(testVoiceBlob);
    testVoiceIns.play();
  } else if (supportSpe) {
    speechSynthesis.speak(testVoiceIns);
  }
};
const pauseTestVoice = () => {
  if (testVoiceIns) {
    if (testVoiceIns && testVoiceIns instanceof Audio) {
      testVoiceIns.pause();
    } else if (supportSpe) {
      speechSynthesis.pause();
    }
  }
  testVoiceBtn.className = "justSetLine resumeTestVoice";
};
const resumeTestVoice = () => {
  if (testVoiceIns) {
    if (testVoiceIns && testVoiceIns instanceof Audio) {
      testVoiceIns.play();
    } else if (supportSpe) {
      speechSynthesis.resume();
    }
  }
  testVoiceBtn.className = "justSetLine pauseTestVoice";
};
const stopTestVoice = () => {
  if (testVoiceIns) {
    if (testVoiceIns instanceof Audio) {
      testVoiceIns.pause();
      testVoiceIns.currentTime = 0;
      URL.revokeObjectURL(testVoiceIns.src);
      testVoiceIns.removeAttribute("src");
    } else if (supportSpe) {
      speechSynthesis.cancel();
    }
  }
  testVoiceBtn.className = "justSetLine readyTestVoice";
};
const startTestVoice = async () => {
  testVoiceBtn.className = "justSetLine pauseTestVoice";
  let volume = voiceVolume[voiceType];
  let rate = voiceRate[voiceType];
  let pitch = voicePitch[voiceType];
  let content = voiceTestText;
  if (existVoice === 4) {
    let voice = voiceRole[voiceType].name;
    let key = content + voice + rate;
    let blob = voiceData[key];
    if (blob) {
      testVoiceBlob = blob;
      playTestAudio();
    } else {
      testVoiceBlob = await getOpenAIVoice(content, voice, rate);
      if (testVoiceBlob) {
        voiceData[key] = testVoiceBlob;
        playTestAudio();
      }
    }
  } else if (existVoice >= 2) {
    let voice =
      existVoice === 3
        ? voiceRole[voiceType].ShortName
        : voiceRole[voiceType].Name;
    let style = azureStyle[voiceType];
    let role = azureRole[voiceType];
    let key =
      content +
      voice +
      volume +
      rate +
      pitch +
      (style ? style : "") +
      (role ? role : "");
    let blob = voiceData[key];
    if (blob) {
      testVoiceBlob = blob;
      playTestAudio();
    } else {
      await initDownSocket();
      let currDate = getTime();
      let lang = voiceRole[voiceType].lang;
      let uuid = uuidv4(existVoice === 3);
      if (existVoice === 3) {
        downSocket.send(getWSPre(currDate, uuid));
      }
      downSocket.send(getWSAudio(currDate, uuid));
      downSocket.send(
        getWSText(
          currDate,
          uuid,
          lang,
          voice,
          volume,
          rate,
          pitch,
          style,
          role,
          content
        )
      );
      downSocket["pending"] = true;
      downQuene[uuid] = {};
      downQuene[uuid]["name"] = content;
      downQuene[uuid]["key"] = key;
      downQuene[uuid]["isTest"] = true;
      downQuene[uuid]["blob"] = [];
    }
  } else {
    testVoiceIns = new SpeechSynthesisUtterance();
    testVoiceIns.onend = testVoiceIns.onerror = () => {
      stopTestVoice();
    };
    testVoiceIns.voice = voiceRole[voiceType];
    testVoiceIns.volume = volume;
    testVoiceIns.rate = rate;
    testVoiceIns.pitch = pitch;
    testVoiceIns.text = content;
    playTestAudio();
  }
};
const downloadAudio = async (idx) => {
  if (existVoice < 2) {
    return;
  }
  let type = data[idx].role === "user" ? 0 : 1;
  let content =
    chatlog.children[systemRole ? idx - 1 : idx].children[1].innerText.trim();
  let rate = voiceRate[type];
  if (existVoice === 4) {
    let voice = voiceRole[type].name;
    let key = content + voice + rate;
    let blob = voiceData[key];
    if (blob) {
      downBlob(blob, content.slice(0, 16) + openAIVoiceSuffix);
    } else {
      let resBlob = await getOpenAIVoice(content, voice, rate);
      if (resBlob) {
        voiceData[key] = resBlob;
        downBlob(voiceData[key], content.slice(0, 16) + openAIVoiceSuffix);
      }
    }
  } else {
    let voice =
      existVoice === 3 ? voiceRole[type].ShortName : voiceRole[type].Name;
    let volume = voiceVolume[type];
    let pitch = voicePitch[type];
    let style = azureStyle[type];
    let role = azureRole[type];
    let key =
      content +
      voice +
      volume +
      rate +
      pitch +
      (style ? style : "") +
      (role ? role : "");
    let blob = voiceData[key];
    if (blob) {
      downBlob(blob, content.slice(0, 16) + voiceSuffix);
    } else {
      await initDownSocket();
      let currDate = getTime();
      let lang = voiceRole[type].lang;
      let uuid = uuidv4(existVoice === 3);
      if (existVoice === 3) {
        downSocket.send(getWSPre(currDate, uuid));
      }
      downSocket.send(getWSAudio(currDate, uuid));
      downSocket.send(
        getWSText(
          currDate,
          uuid,
          lang,
          voice,
          volume,
          rate,
          pitch,
          style,
          role,
          content
        )
      );
      downSocket["pending"] = true;
      downQuene[uuid] = {};
      downQuene[uuid]["name"] = content;
      downQuene[uuid]["key"] = key;
      downQuene[uuid]["blob"] = [];
    }
  }
};
const NoMSEPending = (key) => {
  return new Promise((res, rej) => {
    let bufArray = [];
    voiceSocket.onmessage = (e) => {
      if (e.data instanceof ArrayBuffer) {
        bufArray.push(e.data.slice(voicePreLen));
      } else if (e.data.indexOf("Path:turn.end") !== -1) {
        voiceSocket["pending"] = false;
        if (!(bufArray.length === 1 && bufArray[0].byteLength === 0)) {
          voiceData[key] = new Blob(bufArray, { type: voiceMIME });
          res(voiceData[key]);
        } else {
          res(new Blob());
        }
      }
    };
  });
};
const pauseEv = () => {
  if (voiceIns.src) {
    let ele =
      chatlog.children[systemRole ? currentVoiceIdx - 1 : currentVoiceIdx]
        .lastChild.lastChild;
    ele.classList.remove("readyVoice");
    ele.classList.remove("pauseVoice");
    ele.classList.add("resumeVoice");
  }
};
const resumeEv = () => {
  if (voiceIns.src) {
    let ele =
      chatlog.children[systemRole ? currentVoiceIdx - 1 : currentVoiceIdx]
        .lastChild.lastChild;
    ele.classList.remove("readyVoice");
    ele.classList.remove("resumeVoice");
    ele.classList.add("pauseVoice");
  }
};
const speechEvent = async (idx) => {
  if (!data[idx]) return;
  endSpeak();
  currentVoiceIdx = idx;
  if (!data[idx].content && enableContVoice) {
    if (currentVoiceIdx !== data.length - 1) {
      return speechEvent(currentVoiceIdx + 1);
    } else {
      return endSpeak();
    }
  }
  let type = data[idx].role === "user" ? 0 : 1;
  chatlog.children[systemRole ? idx - 1 : idx].classList.add("showVoiceCls");
  let voiceIconEle =
    chatlog.children[systemRole ? idx - 1 : idx].lastChild.lastChild;
  voiceIconEle.className = "voiceCls pauseVoice";
  let content =
    chatlog.children[systemRole ? idx - 1 : idx].children[1].innerText.trim();
  let volume = voiceVolume[type];
  let rate = voiceRate[type];
  let pitch = voicePitch[type];
  let style = azureStyle[type];
  let role = azureRole[type];
  if (existVoice >= 2) {
    if (!voiceIns || voiceIns instanceof Audio === false) {
      voiceIns = new Audio();
      voiceIns.onpause = pauseEv;
      voiceIns.onplay = resumeEv;
    }
    if (existVoice === 4) {
      let voice = voiceRole[type].name;
      let key = content + voice + rate;
      let currData = voiceData[key];
      if (currData) {
        voiceIns.src = URL.createObjectURL(currData);
      } else {
        let blob = await getOpenAIVoice(content, voice, rate);
        if (blob) {
          voiceData[key] = blob;
          voiceIns.src = URL.createObjectURL(blob);
        } else return;
      }
    } else {
      let voice =
        existVoice === 3 ? voiceRole[type].ShortName : voiceRole[type].Name;
      let key =
        content +
        voice +
        volume +
        rate +
        pitch +
        (style ? style : "") +
        (role ? role : "");
      let currData = voiceData[key];
      if (currData) {
        voiceIns.src = URL.createObjectURL(currData);
      } else {
        let mediaSource;
        if (supportMSE) {
          mediaSource = new MediaSource();
          voiceIns.src = URL.createObjectURL(mediaSource);
          await initStreamVoice(mediaSource);
          if (!sourceBuffer) {
            sourceBuffer = mediaSource.addSourceBuffer(voiceMIME);
          }
          sourceBuffer.onupdateend = function () {
            speechPushing = false;
            if (speechQuene.length) {
              let buf = speechQuene.shift();
              if (buf["end"]) {
                if (!sourceBuffer.buffered.length)
                  notyf.open({
                    type: "warning",
                    message: translations[locale]["cantSpeechTip"],
                  });
                mediaSource.endOfStream();
              } else {
                speechPushing = true;
                sourceBuffer.appendBuffer(buf);
              }
            }
          };
          let bufArray = [];
          voiceSocket.onmessage = (e) => {
            if (e.data instanceof ArrayBuffer) {
              let buf = e.data.slice(voicePreLen);
              bufArray.push(buf);
              speechQuene.push(buf);
            } else if (e.data.indexOf("Path:turn.end") !== -1) {
              voiceSocket["pending"] = false;
              if (!(bufArray.length === 1 && bufArray[0].byteLength === 0))
                voiceData[key] = new Blob(bufArray, { type: voiceMIME });
              if (!speechQuene.length && !speechPushing) {
                mediaSource.endOfStream();
              } else {
                let buf = new ArrayBuffer();
                buf["end"] = true;
                speechQuene.push(buf);
              }
            }
          };
        } else {
          await initSocket();
        }
        let currDate = getTime();
        let lang = voiceRole[type].lang;
        let uuid = uuidv4(existVoice === 3);
        if (existVoice === 3) {
          voiceSocket.send(getWSPre(currDate, uuid));
        }
        voiceSocket.send(getWSAudio(currDate, uuid));
        voiceSocket.send(
          getWSText(
            currDate,
            uuid,
            lang,
            voice,
            volume,
            rate,
            pitch,
            style,
            role,
            content
          )
        );
        voiceSocket["pending"] = true;
        if (!supportMSE) {
          let blob = await NoMSEPending(key);
          if (blob.size === 0)
            notyf.open({
              type: "warning",
              message: translations[locale]["cantSpeechTip"],
            });
          voiceIns.src = URL.createObjectURL(blob);
        }
      }
    }
  } else {
    voiceIns = new SpeechSynthesisUtterance();
    voiceIns.voice = voiceRole[type];
    voiceIns.volume = volume;
    voiceIns.rate = rate;
    voiceIns.pitch = pitch;
    voiceIns.text = content;
  }
  await speakEvent(voiceIns);
  if (enableContVoice) {
    if (currentVoiceIdx !== data.length - 1) {
      return speechEvent(currentVoiceIdx + 1);
    } else {
      endSpeak();
    }
  }
};
let autoVoiceSocket;
let autoMediaSource;
let voiceContentQuene = [];
let voiceEndFlagQuene = [];
let voiceBlobURLQuene = [];
let autoOnlineVoiceFlag = false;
const autoAddQuene = () => {
  if (voiceContentQuene.length) {
    let content = getUnescape(md.render(voiceContentQuene.shift()));
    let currDate = getTime();
    let uuid = uuidv4(existVoice === 3);
    let voice = voiceRole[1].Name;
    if (existVoice === 3) {
      autoVoiceSocket.send(getWSPre(currDate, uuid));
      voice = voiceRole[1].ShortName;
    }
    autoVoiceSocket.send(getWSAudio(currDate, uuid));
    autoVoiceSocket.send(
      getWSText(
        currDate,
        uuid,
        voiceRole[1].lang,
        voice,
        voiceVolume[1],
        voiceRate[1],
        voicePitch[1],
        azureStyle[1],
        azureRole[1],
        content
      )
    );
    autoVoiceSocket["pending"] = true;
    autoOnlineVoiceFlag = true;
  }
};
let autoPlayingIdx = 0;
const autoDirectAddQuene = async (index) => {
  if (voiceContentQuene.length) {
    let content = getUnescape(
      md.render(voiceContentQuene[voiceContentQuene.length - 1])
    );
    let voice = voiceRole[1].name;
    let rate = voiceRate[1];
    let blob;
    if (content !== "" && (blob = await getOpenAIVoice(content, voice, rate))) {
      let blobURL = URL.createObjectURL(blob);
      if (!voiceIns.src && autoPlayingIdx === index) {
        voiceIns.src = blobURL;
        voiceIns.play();
      } else voiceBlobURLQuene[index] = blobURL;
    } else {
      if (!voiceIns.src && autoPlayingIdx === index) autoPlayNext();
      else voiceBlobURLQuene[index] = null;
    }
    if (voiceEndFlagQuene.shift()) {
      if (!voiceIns.src) endSpeak();
      else voiceBlobURLQuene.push("end");
    }
  }
};
const autoPlayNext = () => {
  autoPlayingIdx += 1;
  if (voiceBlobURLQuene.length) {
    let src = voiceBlobURLQuene[autoPlayingIdx];
    if (src === "end") {
      endSpeak();
    } else if (src === null) {
      autoPlayNext();
    } else if (src) {
      voiceIns.src = src;
      voiceIns.currentTime = 0;
      voiceIns.play();
    } else {
      voiceIns.currentTime = 0;
      voiceIns.removeAttribute("src");
    }
  } else {
    voiceIns.currentTime = 0;
    voiceIns.removeAttribute("src");
  }
};
const autoSpeechEvent = (content, ele, force = false, end = false) => {
  if (ele.lastChild.lastChild.classList.contains("readyVoice")) {
    ele.classList.add("showVoiceCls");
    ele.lastChild.lastChild.className = "voiceCls pauseVoice";
  }
  if (existVoice >= 2) {
    voiceContentQuene.push(content);
    voiceEndFlagQuene.push(end);
    if (!voiceIns || voiceIns instanceof Audio === false) {
      voiceIns = new Audio();
      voiceIns.onpause = pauseEv;
      voiceIns.onplay = resumeEv;
    }
    if (existVoice === 4) {
      autoDirectAddQuene(voiceContentQuene.length - 1);
      voiceIns.onended = voiceIns.onerror = () => {
        autoPlayNext();
      };
    } else {
      let url = existVoice === 3 ? getAzureWSURL() : edgeTTSURL;
      if (
        !autoVoiceSocket ||
        autoVoiceSocket.readyState > 1 ||
        autoVoiceSocket.url !== url
      ) {
        if (autoVoiceSocket && autoVoiceSocket.readyState === 1)
          autoVoiceSocket.close(1000);
        autoVoiceSocket = new WebSocket(url);
        autoVoiceSocket.binaryType = "arraybuffer";
        autoVoiceSocket.onopen = () => {
          autoAddQuene();
        };
        autoVoiceSocket.onerror = () => {
          autoOnlineVoiceFlag = false;
        };
      }
      let bufArray = [];
      autoVoiceSocket.onmessage = (e) => {
        if (e.data instanceof ArrayBuffer) {
          (supportMSE ? speechQuene : bufArray).push(e.data.slice(voicePreLen));
        } else {
          if (e.data.indexOf("Path:turn.end") !== -1) {
            autoVoiceSocket["pending"] = false;
            autoOnlineVoiceFlag = false;
            if (!supportMSE) {
              let blob = new Blob(bufArray, { type: voiceMIME });
              bufArray = [];
              if (blob.size) {
                let blobURL = URL.createObjectURL(blob);
                if (!voiceIns.src) {
                  voiceIns.src = blobURL;
                  voiceIns.play();
                } else {
                  voiceBlobURLQuene.push(blobURL);
                }
              } else {
                notyf.open({
                  type: "warning",
                  message: translations[locale]["cantSpeechTip"],
                });
              }
              autoAddQuene();
            }
            if (voiceEndFlagQuene.shift()) {
              if (supportMSE) {
                if (!speechQuene.length && !speechPushing) {
                  autoMediaSource.endOfStream();
                } else {
                  let buf = new ArrayBuffer();
                  buf["end"] = true;
                  speechQuene.push(buf);
                }
              } else {
                if (!voiceBlobURLQuene.length && !voiceIns.src) {
                  endSpeak();
                } else {
                  voiceBlobURLQuene.push("end");
                }
              }
            }
            if (supportMSE) {
              autoAddQuene();
            }
          }
        }
      };
      if (!autoOnlineVoiceFlag && autoVoiceSocket.readyState) {
        autoAddQuene();
      }
      if (supportMSE) {
        if (!autoMediaSource) {
          autoMediaSource = new MediaSource();
          autoMediaSource.onsourceopen = () => {
            if (!sourceBuffer) {
              sourceBuffer = autoMediaSource.addSourceBuffer(voiceMIME);
              sourceBuffer.onupdateend = () => {
                speechPushing = false;
                if (speechQuene.length) {
                  let buf = speechQuene.shift();
                  if (buf["end"]) {
                    if (!sourceBuffer.buffered.length)
                      notyf.open({
                        type: "warning",
                        message: translations[locale]["cantSpeechTip"],
                      });
                    autoMediaSource.endOfStream();
                  } else {
                    speechPushing = true;
                    sourceBuffer.appendBuffer(buf);
                  }
                }
              };
            }
          };
        }
        if (!voiceIns.src) {
          voiceIns.src = URL.createObjectURL(autoMediaSource);
          voiceIns.play();
          voiceIns.onended = voiceIns.onerror = () => {
            endSpeak();
          };
        }
      } else {
        voiceIns.onended = voiceIns.onerror = () => {
          if (voiceBlobURLQuene.length) {
            let src = voiceBlobURLQuene.shift();
            if (src === "end") {
              endSpeak();
            } else {
              voiceIns.src = src;
              voiceIns.currentTime = 0;
              voiceIns.play();
            }
          } else {
            voiceIns.currentTime = 0;
            voiceIns.removeAttribute("src");
          }
        };
      }
    }
  } else {
    voiceIns = new SpeechSynthesisUtterance(content);
    voiceIns.volume = voiceVolume[1];
    voiceIns.rate = voiceRate[1];
    voiceIns.pitch = voicePitch[1];
    voiceIns.voice = voiceRole[1];
    speakEvent(voiceIns, force, end);
  }
};
const confirmAction = (prompt) => {
  if (window.confirm(prompt)) {
    return true;
  } else {
    return false;
  }
};
const findLastSpecialCharIndex = (text) => {
  const specialChars = new Set([".", "?", "!", "~", "。", "？", "！", "\n"]);
  for (let i = text.length - 1; i >= 0; i--) {
    if (specialChars.has(text[i])) {
      return i;
    }
  }
  return -1;
};
let currentModelName;
const getModelName = () => {
  return (
    (modelType === 1
      ? customAPIModel
      : modelType === 2
      ? geminiAPIModel
      : claudeAPIModel) || modelVersion
  );
};
let autoVoiceIdx = 0;
let autoVoiceDataIdx;
let refreshIdx;
let currentResEle;
let progressData = "";
const streamGen = async (long) => {
  currentModelName = getModelName();
  controller = new AbortController();
  controllerId = setTimeout(() => {
    notyf.error(translations[locale]["timeoutTip"]);
    stopLoading();
  }, 200000);
  let isRefresh = refreshIdx !== void 0;
  if (isRefresh) {
    currentResEle = chatlog.children[systemRole ? refreshIdx - 1 : refreshIdx];
    if (outOfMsgWindow(currentResEle))
      messagesEle.scrollTo(0, currentResEle.offsetTop);
  } else if (!currentResEle) {
    currentResEle = createConvEle("response", true, modelVersion);
    currentResEle.children[1].innerHTML = "<p class='cursorCls'><br /></p>";
    currentResEle.dataset.loading = true;
    scrollToBottom();
  }
  let idx = isRefresh ? refreshIdx : data.length;
  if (existVoice && enableAutoVoice && !long) {
    if (isRefresh) {
      endSpeak();
      autoVoiceDataIdx = currentVoiceIdx = idx;
    } else if (currentVoiceIdx !== data.length) {
      endSpeak();
      autoVoiceDataIdx = currentVoiceIdx = idx;
    }
  }
  try {
    let dataSlice;
    if (long) {
      idx = isRefresh ? refreshIdx : data.length - 1;
      dataSlice = [data[idx - 1], data[idx]];
      if (systemRole) dataSlice.unshift(data[0]);
    } else {
      let startIdx = idx > contLen ? idx - contLen - 1 : 0;
      dataSlice = data.slice(startIdx, idx);
      if (systemRole && startIdx > 0) dataSlice.unshift(data[0]);
    }
    let headers = { "Content-Type": "application/json" };
    let url, body;
    if (modelType === 1) {
      dataSlice = dataSlice.map((item) => {
        if (item.role === "assistant")
          return { role: item.role, content: item.content };
        else return item;
      });
      url =
        apiHost +
        (apiHost.length && !apiHost.endsWith("/") ? "/" : "") +
        API_URL;
      if (customAPIKey) headers["Authorization"] = "Bearer " + customAPIKey;
      body = JSON.stringify({
        messages: dataSlice,
        model: currentModelName,
        stream: true,
        temperature: roleTemp,
        top_p: roleNature,
      });
    } else if (modelType === 2) {
      dataSlice = dataSlice.map((item) => {
        return {
          role: item.role === "assistant" ? "model" : "user",
          parts: [{ text: item.content }],
        };
      });
      url =
        geminiApiHost +
        (geminiApiHost.length && !geminiApiHost.endsWith("/") ? "/" : "") +
        Gemini_API_URL +
        currentModelName +
        `:streamGenerateContent?key=${geminiAPIKey}`;
      body = JSON.stringify({
        contents: dataSlice,
        generationConfig: {
          temperature: roleTemp,
          topP: roleNature,
        },
      });
    } else {
      let system;
      if (systemRole) {
        system = dataSlice.shift().content;
      }
      dataSlice = dataSlice.map((item) => {
        if (item.role === "assistant")
          return { role: item.role, content: item.content };
        else return item;
      });
      url =
        claudeApiHost +
        (claudeApiHost.length && !claudeApiHost.endsWith("/") ? "/" : "") +
        Claude_API_URL;
      if (claudeAPIKey) headers["x-api-key"] = claudeAPIKey;
      headers["anthropic-version"] = "2023-06-01";
      body = JSON.stringify({
        model: currentModelName,
        messages: dataSlice,
        max_tokens: 4096,
        stream: true,
        temperature: roleTemp,
        top_p: roleNature,
        ...(system ? { system } : {}),
      });
    }
    const res = await fetch(url, {
      method: "POST",
      headers,
      body,
      signal: controller.signal,
    });
    clearTimeout(controllerId);
    controllerId = void 0;
    if (res.status !== 200) {
      if (res.status === 401) {
        notyf.error(translations[locale]["errorAiKeyTip"]);
      } else if (res.status === 400 || res.status === 413) {
        notyf.error(translations[locale]["largeReqTip"]);
      } else if (res.status === 404) {
        notyf.error(translations[locale]["noModelPerTip"]);
      } else if (res.status === 429) {
        notyf.error(
          res.statusText
            ? translations[locale]["apiRateTip"]
            : translations[locale]["exceedLimitTip"]
        );
      } else {
        notyf.error(translations[locale]["badGateTip"]);
      }
      stopLoading();
      return;
    }
    let container = document.createElement("div");
    const decoder = new TextDecoder();
    const reader = res.body.getReader();
    let readChunk;
    if (modelType === 1) {
      readChunk = async () => {
        return reader.read().then(async ({ value, done }) => {
          if (!done) {
            value = decoder.decode(value);
            let chunks = value.match(/[^\n]+/g);
            if (!chunks) return readChunk();
            let payload;
            for (let i = 0; i < chunks.length; i++) {
              let chunk = chunks[i];
              if (chunk) {
                try {
                  payload = JSON.parse(chunk.slice(5));
                } catch (e) {
                  break;
                }
                if (!payload.choices.length) continue;
                if (payload.choices[0].finish_reason) {
                  let lenStop = payload.choices[0].finish_reason === "length";
                  longReplyFlag = enableLongReply && lenStop;
                  let ele = currentResEle.lastChild.children[0].children[0];
                  if (!enableLongReply && lenStop) {
                    ele.className = "halfRefReq optionItem";
                    ele.title = translations[locale]["continue"];
                  } else {
                    ele.className = "refreshReq optionItem";
                    ele.title = translations[locale]["refresh"];
                  }
                  if (
                    existVoice &&
                    enableAutoVoice &&
                    currentVoiceIdx === autoVoiceDataIdx
                  ) {
                    let voiceText = longReplyFlag
                        ? ""
                        : progressData.slice(autoVoiceIdx),
                      stop = !longReplyFlag;
                    autoSpeechEvent(voiceText, currentResEle, false, stop);
                  }
                  break;
                } else {
                  let content = payload.choices[0].delta.content;
                  if (content) {
                    if (!progressData && !content.trim()) continue;
                    if (
                      existVoice &&
                      enableAutoVoice &&
                      currentVoiceIdx === autoVoiceDataIdx
                    ) {
                      let spliter = content.match(/\.|\?|!|~|。|？|！|\n/);
                      if (spliter) {
                        let voiceText =
                          progressData.slice(autoVoiceIdx) +
                          content.slice(0, spliter.index + 1);
                        autoVoiceIdx += voiceText.length;
                        autoSpeechEvent(voiceText, currentResEle);
                      }
                    }
                    if (progressData && textSpeed) await delay();
                    progressData += content;
                    container.innerHTML = md.render(progressData);
                    if (
                      container.children.length > 1 &&
                      currentResEle.children[1].children.length ===
                        container.children.length
                    ) {
                      morphdom(
                        currentResEle.children[1].lastElementChild,
                        container.lastElementChild
                      );
                    } else {
                      morphdom(currentResEle.children[1], container, {
                        childrenOnly: true,
                      });
                    }
                    scrollToBottom();
                  }
                }
              }
            }
            return readChunk();
          } else {
            if (isRefresh) {
              data[refreshIdx].content = progressData;
              data[refreshIdx].model = currentModelName;
            } else {
              if (long) {
                data[data.length - 1].content = progressData;
              } else {
                data.push({
                  role: "assistant",
                  content: progressData,
                  model: currentModelName,
                });
              }
            }
            if (longReplyFlag) return streamGen(true);
            stopLoading(false);
          }
        });
      };
    } else if (modelType === 2) {
      let stop_reason;
      let endGeminiFunc = () => {
        let lenStop = stop_reason === "MAX_TOKENS";
        longReplyFlag = enableLongReply && lenStop;
        let ele = currentResEle.lastChild.children[0].children[0];
        if (!enableLongReply && lenStop) {
          ele.className = "halfRefReq optionItem";
          ele.title = translations[locale]["continue"];
        } else {
          ele.className = "refreshReq optionItem";
          ele.title = translations[locale]["refresh"];
        }
        if (
          existVoice &&
          enableAutoVoice &&
          currentVoiceIdx === autoVoiceDataIdx
        ) {
          let voiceText = longReplyFlag ? "" : progressData.slice(autoVoiceIdx),
            stop = !longReplyFlag;
          autoSpeechEvent(voiceText, currentResEle, false, stop);
        }
      };
      readChunk = async () => {
        return reader.read().then(async ({ value, done }) => {
          if (!done) {
            value = decoder.decode(value);
            let payload;
            try {
              let startIdx = value.indexOf("{");
              let endIdx = value.lastIndexOf("}");
              payload = JSON.parse(value.slice(startIdx, endIdx + 1));
            } catch (e) {
              if (value === "]") endGeminiFunc();
              return readChunk();
            }
            let content = payload.candidates[0].content.parts[0].text;
            if (content) {
              if (!progressData && !content.trim()) return readChunk();
              if (
                existVoice &&
                enableAutoVoice &&
                currentVoiceIdx === autoVoiceDataIdx
              ) {
                let spliter = findLastSpecialCharIndex(content);
                if (spliter != -1) {
                  let voiceText =
                    progressData.slice(autoVoiceIdx) +
                    content.slice(0, spliter + 1);
                  autoVoiceIdx += voiceText.length;
                  autoSpeechEvent(voiceText, currentResEle);
                }
              }
              if (progressData && textSpeed) await delay();
              progressData += content;
              container.innerHTML = md.render(progressData);
              if (
                container.children.length > 1 &&
                currentResEle.children[1].children.length ===
                  container.children.length
              ) {
                morphdom(
                  currentResEle.children[1].lastElementChild,
                  container.lastElementChild
                );
              } else {
                morphdom(currentResEle.children[1], container, {
                  childrenOnly: true,
                });
              }
              scrollToBottom();
              stop_reason = payload.candidates[0].finishReason;
              if (value[value.length - 1] === "]") endGeminiFunc();
            }
            return readChunk();
          } else {
            if (isRefresh) {
              data[refreshIdx].content = progressData;
              data[refreshIdx].model = currentModelName;
            } else {
              if (long) {
                data[data.length - 1].content = progressData;
              } else {
                data.push({
                  role: "assistant",
                  content: progressData,
                  model: currentModelName,
                });
              }
            }
            if (longReplyFlag) return streamGen(true);
            stopLoading(false);
          }
        });
      };
    } else {
      readChunk = async () => {
        return reader.read().then(async ({ value, done }) => {
          if (!done) {
            value = decoder.decode(value);
            let chunks = value.match(/[^\n]+/g);
            if (!chunks) return readChunk();
            let event;
            let payload;
            for (let i = 0; i < chunks.length; i++) {
              let chunk = chunks[i];
              if (chunk) {
                try {
                  if (chunk.startsWith("event")) {
                    event = chunk.slice(7);
                  } else {
                    payload = JSON.parse(chunk.slice(5));
                  }
                } catch (e) {
                  break;
                }
                if (chunk.startsWith("event")) continue;
                if (event === "message_delta") {
                  let lenStop = payload.delta.stop_reason === "max_tokens";
                  longReplyFlag = enableLongReply && lenStop;
                  let ele = currentResEle.lastChild.children[0].children[0];
                  if (!enableLongReply && lenStop) {
                    ele.className = "halfRefReq optionItem";
                    ele.title = translations[locale]["continue"];
                  } else {
                    ele.className = "refreshReq optionItem";
                    ele.title = translations[locale]["refresh"];
                  }
                  if (
                    existVoice &&
                    enableAutoVoice &&
                    currentVoiceIdx === autoVoiceDataIdx
                  ) {
                    let voiceText = longReplyFlag
                        ? ""
                        : progressData.slice(autoVoiceIdx),
                      stop = !longReplyFlag;
                    autoSpeechEvent(voiceText, currentResEle, false, stop);
                  }
                  break;
                } else if (event === "content_block_delta") {
                  if (payload.delta.type !== "text_delta") continue;
                  let content = payload.delta.text;
                  if (content) {
                    if (!progressData && !content.trim()) continue;
                    if (
                      existVoice &&
                      enableAutoVoice &&
                      currentVoiceIdx === autoVoiceDataIdx
                    ) {
                      let spliter = content.match(/\.|\?|!|~|。|？|！|\n/);
                      if (spliter) {
                        let voiceText =
                          progressData.slice(autoVoiceIdx) +
                          content.slice(0, spliter.index + 1);
                        autoVoiceIdx += voiceText.length;
                        autoSpeechEvent(voiceText, currentResEle);
                      }
                    }
                    if (progressData && textSpeed) await delay();
                    progressData += content;
                    container.innerHTML = md.render(progressData);
                    if (
                      container.children.length > 1 &&
                      currentResEle.children[1].children.length ===
                        container.children.length
                    ) {
                      morphdom(
                        currentResEle.children[1].lastElementChild,
                        container.lastElementChild
                      );
                    } else {
                      morphdom(currentResEle.children[1], container, {
                        childrenOnly: true,
                      });
                    }
                    scrollToBottom();
                  }
                }
              }
            }
            return readChunk();
          } else {
            if (isRefresh) {
              data[refreshIdx].content = progressData;
              data[refreshIdx].model = currentModelName;
            } else {
              if (long) {
                data[data.length - 1].content = progressData;
              } else {
                data.push({
                  role: "assistant",
                  content: progressData,
                  model: currentModelName,
                });
              }
            }
            if (longReplyFlag) return streamGen(true);
            stopLoading(false);
          }
        });
      };
    }
    await readChunk();
    container = null;
  } catch (e) {
    if (e.message.indexOf("aborted") === -1) {
      notyf.error(translations[locale]["badEndpointTip"]);
      stopLoading();
    }
  }
};
const loadAction = (bool) => {
  loading = bool;
  sendBtnEle.disabled = bool;
  sendBtnEle.className = bool ? " loading" : "loaded";
  stopEle.style.display = bool ? "flex" : "none";
  textInputEvent();
};
const updateChatPre = () => {
  let ele = activeChatEle.children[1].children[1];
  let first = data.find((item) => {
    return item.role === "assistant";
  });
  ele.textContent = first ? first.content.slice(0, 30) : "";
  forceRepaint(ele.parentElement);
};
const stopLoading = (abort = true) => {
  stopEle.style.display = "none";
  if (currentResEle.children[1].querySelector(".cursorCls"))
    currentResEle.children[1].innerHTML = "<br />";
  if (abort) {
    controller.abort();
    if (controllerId) clearTimeout(controllerId);
    if (delayId) clearTimeout(delayId);
    if (refreshIdx !== void 0) {
      data[refreshIdx].content = progressData;
      data[refreshIdx].model = currentModelName;
    } else if (data[data.length - 1].role === "assistant") {
      data[data.length - 1].content = progressData;
      data[data.length - 1].model = currentModelName;
    } else {
      data.push({
        role: "assistant",
        content: progressData,
        model: currentModelName,
      });
    }
    if (
      existVoice &&
      enableAutoVoice &&
      currentVoiceIdx === autoVoiceDataIdx &&
      progressData.length
    ) {
      let voiceText = progressData.slice(autoVoiceIdx);
      autoSpeechEvent(voiceText, currentResEle, false, true);
    }
  }
  if (activeChatEle.children[1].children[1].textContent === "") updateChatPre();
  updateChats();
  controllerId = delayId = refreshIdx = autoVoiceDataIdx = void 0;
  autoVoiceIdx = 0;
  currentResEle.dataset.loading = false;
  currentResEle = null;
  progressData = "";
  loadAction(false);
};
const generateText = (message) => {
  loadAction(true);
  let requestEle;
  let isBottom = isContentBottom();
  if (editingIdx !== void 0) {
    let idx = editingIdx;
    let eleIdx = systemRole ? idx - 1 : idx;
    requestEle = chatlog.children[eleIdx];
    data[idx].content = message;
    resumeSend();
    if (idx !== data.length - 1) {
      requestEle.children[1].textContent = message;
      if (data[idx + 1].role !== "assistant") {
        if (currentVoiceIdx !== void 0) {
          if (currentVoiceIdx > idx) {
            currentVoiceIdx++;
          }
        }
        data.splice(idx + 1, 0, {
          role: "assistant",
          content: "",
          model: modelVersion,
        });
        chatlog.insertBefore(
          createConvEle("response", false, modelVersion),
          chatlog.children[eleIdx + 1]
        );
      } else
        formatAvatarEle(chatlog.children[eleIdx + 1].children[0], modelVersion);
      chatlog.children[eleIdx + 1].children[1].innerHTML =
        "<p class='cursorCls'><br /></p>";
      chatlog.children[eleIdx + 1].dataset.loading = true;
      idx = idx + 1;
      data[idx].content = "";
      if (idx === currentVoiceIdx) {
        endSpeak();
      }
      refreshIdx = idx;
      updateChats();
      streamGen();
      return;
    }
  } else {
    requestEle = createConvEle("request");
    data.push({ role: "user", content: message });
  }
  requestEle.children[1].textContent = message;
  if (chatsData[activeChatIdx].name === translations[locale]["newChatName"]) {
    if (message.length > 20) message = message.slice(0, 17) + "...";
    chatsData[activeChatIdx].name = message;
    activeChatEle.children[1].children[0].textContent = message;
  }
  updateChats();
  if (isBottom) messagesEle.scrollTo(0, messagesEle.scrollHeight);
  streamGen();
};
inputAreaEle.onkeydown = (e) => {
  if (e.keyCode === 13 && !e.shiftKey) {
    e.preventDefault();
    genFunc();
  } else if (keepListenMic && recing) {
    resetRecRes();
  }
};
const genFunc = async function () {
  clearAutoSendTimer();
  if (recing) {
    if (existRec === 3) await toggleRecEv(false);
    else if (existRec === 2) toggleRecEv(false);
    else if (!keepListenMic) toggleRecEv();
  }
  let message = inputAreaEle.value.trim();
  if (message.length !== 0 && noLoading()) {
    inputAreaEle.value = "";
    inputAreaEle.style.height = "47px";
    if (keepListenMic && recing) resetRecRes();
    generateText(message);
  }
};
sendBtnEle.onclick = genFunc;
stopEle.onclick = stopLoading;
clearEle.onclick = () => {
  if (editingIdx === void 0) {
    if (noLoading() && confirmAction(translations[locale]["clearChatTip"])) {
      endSpeak();
      if (systemRole) {
        data.length = 1;
      } else {
        data.length = 0;
      }
      chatlog.innerHTML = "";
      updateChatPre();
      updateChats();
    }
  } else {
    resumeSend();
  }
};

document.querySelectorAll("#questionList li").forEach(function (item) {
  item.addEventListener("click", function () {
    document.getElementById(
      "response"
    ).innerText = `你选择了: ${this.innerText}`;
  });
});
